← Build log

One Ring - I mean, Settings - to Rule Them All: Resolving the Dual Storage Anti-Pattern

Part 2 of “The Tale of Two Settings” - December 7, 2024


The Resolution: From Chaos to Order

Remember that sticky dual storage anti-pattern I wrote about? The one where our paragraph break presets were living a double life across two different files? Well, I’m happy to report that particular dragon has been slain.

And here’s the kicker: it was surprisingly easy to fix once we actually identified what was wrong.

The “One Ring” Solution

The fix was elegantly simple - create a single source of truth. We introduced config/preset-constants.ts that became our “One Ring to Rule Them All”:

export const PARAGRAPH_PRESETS = {
  MAX_BREAKS: {
    name: 'Maximum Breaks',
    description: 'Most breaks - good for lists and structured content',
    scoreThreshold: 0.1,
    minBreakDistance: 3,
    pauseThreshold: 150,
    smoothingRatio: 6, // 1 break per 6 words max
  },
  FREQUENT: {
    name: 'Frequent Breaks',
    description: 'Regular breaks - good for presentations',
    scoreThreshold: 0.2,
    minBreakDistance: 3,
    pauseThreshold: 175,
    smoothingRatio: 6,
  },
  BALANCED: {
    name: 'Balanced',
    description: 'Balanced approach - good for most content',
    scoreThreshold: 0.8,
    minBreakDistance: 8,
    pauseThreshold: 300,
    smoothingRatio: 10,
  },
} as const;

Then both files simply import from this shared constant. 47+ lines of duplicate code eliminated. The GUI preset selection now actually matches the formatting behavior. Problem solved.

The Scary-Easy Pattern

Here’s what’s genuinely unsettling about this whole experience: it’s frighteningly easy for this anti-pattern to emerge.

Picture this common scenario:

  1. You have some configuration in File A
  2. You need similar (but slightly different) logic in File B
  3. You’re in a hurry, so you copy-paste and modify
  4. Time passes, requirements change
  5. You update File A but forget about File B
  6. Your users start experiencing inconsistent behavior
  7. You spend way too long debugging what should be a simple settings issue

Sound familiar? This is basically the software development equivalent of leaving your keys in the fridge - it happens to everyone, and it’s always more obvious in hindsight.

The Real Villain: Insufficient Testing

But here’s the deeper issue this uncovered: we don’t have enough regression tests.

This dual storage problem existed for who knows how long because we weren’t testing the end-to-end behavior properly. Our tests were focused on individual components, but we weren’t verifying that “when a user selects preset X in the GUI, the actual formatting behavior matches preset X.”

That’s a gap. A big one.

We were essentially flying blind on user experience consistency. Changes could introduce regressions, and we wouldn’t know until a user reported weird behavior (which, thankfully, our beta tester did).

The Testing Wake-Up Call

This incident has put regression testing at the top of our priority list. Over the next couple of days, we’re going to tackle:

  1. End-to-end preset testing - Verify that GUI selections produce expected formatting
  2. Cross-component consistency tests - Ensure settings changes propagate correctly
  3. User journey validation - Test the complete flow from selection to result

The irony? Adding these tests will probably take longer than fixing the original bug did. But that’s the nature of technical debt - the fix is often simple, but preventing future occurrences requires systematic investment.

Lessons from the Resolution

1. Identity Problems Early

The hardest part wasn’t solving the problem - it was recognizing that we had inconsistent behavior in the first place. User feedback was crucial here.

2. Single Source of Truth is King

When you find yourself copy-pasting configuration or constants, that’s your cue to create a shared module. Future you will thank present you.

3. Test the User Experience, Not Just the Code

Unit tests are great, but they don’t catch integration issues. You need tests that verify the complete user journey.

4. Technical Debt Compounds

This wasn’t just a “settings are in two places” problem - it was a “we don’t have sufficient testing to catch these issues” problem. One piece of debt revealed another.

The Bigger Picture

This whole experience reinforces something I’ve learned building VoiceCatcher: good software architecture isn’t about being clever - it’s about being consistent and testable.

The fancy algorithms and ML models get all the attention, but it’s often the boring stuff like “where do we store our configuration constants” that determines whether users have a smooth experience or a frustrating one.

Our users don’t care about our internal architecture. They just want the thing they clicked to actually do what it says it will do. And frankly, that’s exactly how it should be.

What’s Next

The dual storage anti-pattern is resolved, but the lesson remains: easy problems require systematic solutions. We’re investing in the testing infrastructure that will catch these issues before they reach users.

Sometimes the most important engineering work isn’t the features that make it into release notes - it’s the invisible foundation that ensures everything else works as expected.

And honestly? That’s probably the most important kind of work we can be doing right now.


Building VoiceCatcher has been a masterclass in learning that the simplest problems often reveal the most important gaps in your development process. Sometimes you need the easy fixes to show you where the hard work actually needs to happen.