← Build log

Building VoiceCatcher: A Journey Through Modern JavaScript Complexity

January 2025
JavaScriptElectronTypeScriptESMBuild ToolsLessons Learned

Or: “How I Learned to Stop Worrying and Love the Build Pipeline”

Published: January 2025
Author: VoiceCatcher Development Team
Tags: JavaScript, Electron, TypeScript, ESM, Build Tools, Lessons Learned


🎯 Strategic Product Management Perspective: Technical Complexity as Business Intelligence and Market Positioning

The Technical Product Manager’s Complexity Management Framework

From a 20-year Technical Product Manager perspective, managing technical complexity isn’t just about engineering best practices—it’s about organizational capability development and competitive market positioning. Traditional product management treats technical complexity as “engineering overhead.” AI-enhanced TPMs recognize complexity management as measurable business capability and market resilience.

The Universal Business Challenge: Technical Complexity as Market Liability vs. Competitive Advantage

Traditional PM Approach:

  • Technical complexity = development cost center
  • “Why is development taking so long?” (no complexity-capability assessment)
  • Build tooling decisions without business context
  • Engineering choices as cost rather than competitive capability

AI-Enhanced TPM Analysis:

# Objective complexity ROI quantification
6 months complexity investment = enterprise-grade development foundation
3,000 lines strategic codebase = sustainable competitive capability
32 npm scripts optimization = development velocity competitive advantage
ROI = Market readiness + engineering competency + competitive resilience

Result: Strategic complexity investment with quantified business capability and competitive advantage value.

Strategic Technical Complexity Framework

Layer 1: Business Capability Development

  • Enterprise-grade foundation: Complex tooling enables professional market positioning
  • Development velocity optimization: Build systems as competitive development advantage
  • Quality assurance capabilities: Testing foundations for business risk management
  • Scaling preparation: Architecture decisions for market opportunity capture

Layer 2: Competitive Technical Positioning Indicators

  • Engineering excellence reputation: Complex system management as talent attraction
  • Market credibility establishment: Professional development practices for client confidence
  • Partnership readiness: Sophisticated technical foundation for integration opportunities
  • Investment preparedness: Enterprise-grade codebase for funding and acquisition discussions

Layer 3: Strategic Market Position

  • Technical differentiation: Complex system mastery as competitive advantage
  • Client confidence building: Professional development demonstrates reliability
  • Market scalability: Robust foundation supports rapid business growth
  • Innovation capacity: Sophisticated tooling enables competitive feature development

I. The Dream vs. The Reality

The Initial Vision

When we started VoiceCatcher, the concept seemed straightforward: a simple voice-to-text tool that would “just record, transcribe, and paste.” How hard could it be? We envisioned:

  • Press a hotkey to start recording
  • Stop when you finish speaking
  • Get transcribed text instantly in your clipboard
  • Maybe add a simple GUI for settings

The plan was to use modern tools: Electron for cross-platform support, React for the UI, TypeScript for type safety, and ES modules because “that’s the future.” We estimated a few weeks of development.

The Reality Check

Six months later, we have:

  • 70+ npm scripts and counting
  • Post-build scripts that rename files and rewrite import paths
  • ESM/CommonJS compatibility maze that requires custom tooling
  • Multiple TypeScript configs for different execution contexts
  • A 1,085-line main.ts file that became a monolithic monster

Here’s what our build output actually looks like:

> npm run start:gui
Starting post-build processing...
Processing contents of dist/src...
  Replacing in transcriber.js: require("./index.js") -> require("./index.cjs")
  Replacing in recorder.js: require("./index.js") -> require("./index.cjs")
  # ... 20+ more file transformations
Renaming .js files to .cjs...
Renamed dist/src/transcriber.js to dist/src/transcriber.cjs
  # ... 15+ more file renames
Renaming .d.ts files to .d.cts...
  # ... another 15+ type definition renames

This is the story of how we got here, the mistakes we made, and what we learned.


II. Mistake #1: “Modern JavaScript Will Just Work”

The ESM Paradise Lost

We started with the best intentions, setting "type": "module" in our package.json thinking we were embracing the future. ES modules are cleaner, more explicit, and supposedly the standard. What could go wrong?

Everything.

The problems started immediately:

  • Electron still expects CommonJS for the main process in many contexts
  • Third-party libraries have inconsistent ESM support
  • Node.js tooling often assumes CommonJS
  • Build tools have varying levels of ESM compatibility

The Painful Solution

We ended up creating a custom post-build script that literally rewrites our compiled JavaScript:

// scripts/postbuild.ts - Our "ESM to CommonJS" translator
const replacePatterns = [
  { from: /require\("\.\/index\.js"\)/g, to: 'require("./index.cjs")' },
  { from: /require\("\.\/recorder\.js"\)/g, to: 'require("./recorder.cjs")' },
  // ... dozens more patterns
];

The script:

  1. Compiles TypeScript to JavaScript with ES modules
  2. Renames all .js files to .cjs for Electron compatibility
  3. Rewrites all require paths to use .cjs extensions
  4. Does the same for TypeScript definition files (.d.ts → .d.cts)

The Lesson: Sometimes “legacy” formats exist for good reasons. The JavaScript ecosystem’s transition to ESM is real, but incomplete. Choose your battles carefully.


III. Mistake #2: “Tests Can Wait Until Later”

The No-Test Phase

In the beginning, we focused entirely on getting the MVP working. Manual testing seemed sufficient for a “simple” app. After all, it’s just recording audio and calling Whisper.cpp, right?

We told ourselves:

  • “It’s just a prototype”
  • “We’ll add tests when we clean up the code”
  • “Manual testing is faster for now”

The Wake-Up Call

As the application grew, bugs started accumulating:

  • Hotkey changes broke existing functionality in subtle ways
  • UI cursor positioning bugs that only appeared in certain scenarios
  • Settings not persisting correctly across restarts
  • Fear of refactoring anything because we couldn’t be sure what would break

The breaking point came when we realized we were afraid to fix obvious bugs because we couldn’t predict the side effects.

The Testing Transformation

We completely overhauled our approach:

  1. Migrated from Jest to Vitest (Jest + ESM = pain)

  2. Implemented test-driven refactoring strategy

  3. Added tests to the build process:

    {
      "build": "npm test && npm run build:src && npm run build:main && npm run build:gui",
      "build:fast": "npm run build:src && npm run build:main && npm run build:gui"
    }
  4. Created comprehensive test documentation including value assessment

The Lesson: Tests aren’t overhead—they’re confidence. The time “saved” by skipping tests early is paid back with massive interest later.


IV. Mistake #3: “One Big File Will Be Fine”

The Monolithic Main Process

Our main/main.ts file grew to 1,085 lines and became a testament to the “it works, don’t touch it” mentality. It contained:

  • Electron app lifecycle management
  • Tray icon creation and animation
  • Global hotkey registration and handling
  • IPC communication with the renderer
  • Audio recording orchestration
  • Settings management
  • Error handling and logging

Git diffs became incomprehensible. Adding any feature required understanding the entire system. Code reviews were exercises in archeology.

The Refactoring Reality

Before we could refactor, we had to:

  1. Create comprehensive documentation of the existing system
  2. Write retrospective tests for current functionality
  3. Design a modular architecture with clear interfaces
  4. Extract components incrementally using test-driven development

The planned architecture:

main/
├── main.ts              # Orchestration only
├── tray-manager.ts      # Tray icon and menu management
├── recording-controller.ts # Audio recording workflow
├── settings-manager.ts  # Settings persistence and IPC
└── hotkey-handler.ts    # Global hotkey registration

The Lesson: Architecture matters even for “simple” applications. The cost of refactoring grows exponentially with the size of monolithic components.


V. Mistake #4: “Fast Development vs. Proper Process”

The Speed Trap

We created multiple development paths to optimize for speed:

{
  "start:gui": "npm run build:src && npm run build:main && electron .",
  "start:gui:fast": "npm run build:fast && electron .",
  "dev:gui": "vite"
}

The “fast” path bypassed tests, type checking, and other safety measures. It seemed like a good compromise—speed when developing, safety when building for production.

The problem: We always used the fast path.

Finding Balance

We restructured the scripts to make the safe path the default:

{
  "start:gui": "npm run build && electron .", // Safe, with tests
  "start:gui:fast": "npm run build:fast && electron .", // Fast iteration
  "dev:gui": "vite" // Live reloading
}

The Lesson: Provide both safe and fast paths, but make the safe path the default. Developers will take the path of least resistance—design your system accordingly.


VI. Mistake #5: “Documentation Is for Later”

The Undocumented Maze

Our complex build processes had no explanation. Settings worked “somehow.” When team members (including our future selves) tried to understand decisions made weeks earlier, we faced:

  • Build scripts with no comments explaining the “why”
  • Magic environment variables with no documentation
  • Complex TypeScript configurations with no rationale
  • Architectural decisions lost to Slack history

The Documentation Investment

We created comprehensive documentation:

  • docs/build-and-test-process.md - Complete build pipeline explanation
  • docs/technical-design.md - Architecture decisions and rationale
  • docs/project-learnings.md - Mistakes and lessons learned
  • docs/setup-and-installation.md - Step-by-step setup process
  • docs/test-driven-refactoring-strategy.md - Testing approach

There’s a common joke in software engineering: “Jira is where all good docs go to die.” You know the pattern - important decisions get discussed in tickets, crucial context gets buried in comments, and six months later nobody remembers why we made that architectural choice.

But here’s the plot twist: AI changed everything. That same Jira ticket you thought was lost forever? AI finds it instantly. Those scattered comments across multiple tickets? AI connects the dots. The documentation that died in Jira isn’t dead anymore - AI uses it religiously.

This is why building documentation as you go isn’t just about markdown files anymore. Every decision captured anywhere - tickets, commit messages, pull request comments, design docs - becomes part of your AI-accessible knowledge base. The question isn’t whether to document, it’s whether you want AI to help you six months from now or leave you starting from scratch.

The Lesson: Document the “why,” not just the “what.” Future developers (including future you) need context, not just instructions. And now that AI can find and synthesize scattered documentation, every piece of context you capture becomes exponentially more valuable.


VII. The Breakthrough: Embracing Complexity

Accepting Modern JS Reality

We stopped fighting the ecosystem and started working with it:

  • ESM/CommonJS bridges aren’t going away anytime soon
  • Build tooling complexity is the price of modern features
  • TypeScript configurations multiply because different contexts have different needs
  • Complex npm scripts are sometimes the right solution

Building for Resilience

Instead of hiding complexity, we made it manageable:

  1. Comprehensive test coverage with value-based assessment
  2. Build processes that fail fast and provide clear error messages
  3. Development workflows that support both speed and safety
  4. Documentation that explains not just how, but why

The Current State

Today, VoiceCatcher is:

  • 📦 Dual-mode: Both CLI and GUI versions from the same codebase
  • 🧪 Well-tested: Vitest-based testing with watch mode and coverage
  • 🔧 Reliably buildable: Complex but predictable build pipeline
  • 📚 Documented: Comprehensive guides for contributors
  • 🚀 Contribution-ready: Clear processes for open-source development

VIII. What We’d Do Differently

Technical Decisions

  1. Start with CommonJS, migrate to ESM when the ecosystem catches up
  2. Design for testing from day one - don’t retrofit tests later
  3. Modular architecture from the beginning - resist the “one big file” temptation
  4. Document build decisions immediately - don’t rely on memory

Process Improvements

  1. Test-first development for core functionality
  2. Regular architecture reviews before files get too large
  3. Automated dependency audits to catch issues early
  4. Clear separation of development vs. production paths

Tooling Choices

  1. Choose mature tools over bleeding-edge alternatives
  2. Optimize for maintainability over development speed
  3. Invest in tooling that makes complex tasks manageable
  4. Plan for multiple execution contexts from the start

IX. Lessons for Other Developers

The Hard Truths

  • Modern JavaScript tooling is complex - embrace it rather than fight it
  • Every “simple” Electron app becomes complex - plan accordingly
  • ESM/CommonJS compatibility is still a real issue in 2025
  • Testing isn’t optional for anything you plan to maintain

The Practical Advice

  • Choose your build tools carefully and stick with them
  • Invest in documentation early, especially for complex processes
  • Create both fast and safe development paths - make safe the default
  • Test your most critical user journeys first - not everything needs 100% coverage

The Silver Lining

The complexity pays off:

  • Reliability: Our application works consistently across environments
  • Maintainability: Good tooling makes complex tasks manageable
  • Confidence: A well-tested codebase enables fearless refactoring
  • Onboarding: Documentation makes future work possible

🏆 Strategic Product Management Excellence: AI-Enhanced Technical Complexity Leadership

The TPM’s Complexity Management Strategy Advantage

This technical complexity journey demonstrates revolutionary TPM capabilities for engineering excellence leadership:

Traditional PM Complexity Limitations

  • Engineering black box: “Development is taking longer than expected”
  • No capability intelligence: Complexity without business competency context
  • Missing competitive context: Technical decisions without market positioning awareness
  • Reactive complexity management: Address technical debt after it impacts delivery

AI-Enhanced TPM Technical Complexity Leadership

  • Objective complexity assessment: Systematic technical analysis with measurable business impact
  • Strategic capability management: Complexity decisions for competitive advantage positioning
  • Proactive technical strategy: Enterprise-grade foundations before market opportunities require them
  • Business-aligned engineering: Technical complexity as competitive intelligence and market differentiation

Universal Application Framework: Any Software Organization

This methodology transforms technical complexity management across industries:

Discovery Phase: AI-Assisted Technical Complexity Analysis

# Universal technical complexity quantification
find . -name "*.ts" -o -name "*.js" | xargs wc -l | tail -1  # Codebase complexity
npm list --depth=0 | wc -l                                  # Dependency complexity
grep "scripts" package.json -A 50 | grep ":" | wc -l        # Build complexity indicators

TPM Capability: Independent assessment of technical complexity impact on business capability

Prioritization Phase: Business Impact Calculation

Technical Complexity Investment: 6 months → enterprise-grade development foundation
Development Capability: 3,000 lines strategic code + 32 build scripts optimization
Market Positioning: Professional development practices + enterprise client confidence
Competitive Advantage: Technical excellence + engineering talent attraction
Annual Business Value: Development capability + market positioning + competitive advantage

TPM Capability: Quantified business case for strategic complexity investment

Execution Phase: Risk-Managed Technical Strategy

  • Capability-first approach: Technical complexity for business competency development
  • Competitive advantage focus: Enterprise-grade practices for market differentiation
  • Strategic architecture: Technical decisions as business foundation enhancement

TPM Capability: Lead technical initiatives with competitive positioning benefit

Validation Phase: Competitive Technical Capability Advantage

  • Enterprise positioning: Professional development for client confidence and partnership discussions
  • Market differentiation: Technical excellence as competitive advantage
  • Talent acquisition: Engineering sophistication attracts senior professionals
  • Scaling foundation: Robust technical foundation supports rapid business growth

TPM Capability: Transform technical investment into business competitive advantage

Career Transformation: Technical Complexity Leadership Through Strategic Excellence

For Product Managers

Traditional Role: “Why is development so complex and taking so long?” AI-Enhanced Role: “Here’s my analysis of technical complexity competitive positioning and our engineering capability strategy”

Impact: Evolution from complexity observer to strategic technical complexity leader

For Engineering Teams

Traditional Dynamic: PMs don’t understand technical complexity trade-offs AI-Enhanced Dynamic: PMs provide strategic technical direction with business context

Impact: Partnership in technical excellence instead of complexity debates

For Organizations

Traditional Challenge: Technical complexity as development overhead AI-Enhanced Solution: Complexity management as business capability and competitive intelligence operations

Impact: Strategic technical capability as market advantage

Strategic Decision Matrix: Technical Investment vs. Business Value

                    HIGH CAPABILITY IMPACT        LOW CAPABILITY IMPACT
                           ↑                           ↑
HIGH EFFORT       [**TECHNICAL PLATFORM**]      [**OVER-ENGINEERING TRAP**] ←← AVOID
LOW EFFORT        [**CAPABILITY DEVELOPMENT**]  [**COMPLEXITY ACCUMULATION**]
                           ↑ START HERE

TPM Enhancement: Business Context for Technical Decisions

  • Technical Platform: Major engineering improvements enabling competitive advantage
  • Capability Development: High-impact technical protecting market competency
  • Over-Engineering Trap: Technical complexity without business competitive benefit
  • Complexity Accumulation: Technical changes without competitive positioning impact

This matrix enables strategic technical investment aligned with competitive business objectives.

Professional Technical Management: Business-Aligned Complexity Standards

Enhanced TPM Decision Framework

Technical Investment Justified When:

  • ✅ Competitive positioning: Technical capability supports market advantage
  • ✅ Enterprise readiness: Professional development enables client confidence and partnerships
  • ✅ Market differentiation: Technical excellence as competitive advantage
  • ✅ Scaling foundation: Robust architecture supports rapid business growth
  • ✅ Talent acquisition: Engineering sophistication attracts senior professionals

Stop Technical Investment When:

  • ❌ Diminishing returns: Additional complexity yields minimal business capability
  • ❌ Over-engineering pursuit: Technical perfectionism without business competitive benefit
  • ❌ Complexity proliferation: Technical overhead exceeds competitive positioning value

Career Impact: Strategic Technical Leadership

TPMs who master technical complexity management:

  • Trusted for technical strategy: Complexity decisions impact competitive positioning
  • Drive business differentiation: Technical excellence as market strategy
  • Enable strategic opportunities: Engineering capability supports enterprise and partnership conversations
  • Attract top talent: Technical sophistication draws senior engineering professionals

Traditional PMs who remain complexity-passive:

  • Limited technical influence: Can’t contribute to technical strategy decisions
  • Missing competitive opportunities: Don’t leverage technical capability as market advantage
  • Reactive to complexity issues: Address problems after they impact business delivery

X. Conclusion: The Path Forward

What We Built

VoiceCatcher is more than a voice-to-text application. It’s a demonstration of how to manage complexity in modern JavaScript development:

  • It actually works reliably across different systems
  • It can be modified without fear thanks to comprehensive testing
  • It has comprehensive documentation for future contributors
  • It’s ready for community contributions with clear processes

The Real Success

The real success isn’t the application itself, but the process we developed:

  • Resilient build systems that handle complexity gracefully
  • Comprehensive testing strategies that provide genuine value
  • Documentation-driven development that preserves knowledge
  • Balance between speed and safety in development workflows

The Ultimate Lesson

Software complexity is inevitable. The question isn’t whether you’ll have complexity, but whether you’ll manage it intentionally or let it manage you.

We chose to manage it intentionally, and the result is a codebase that’s complex but comprehensible, sophisticated but maintainable.

The Strategic Advantage: Technical Product Management Revolution

This complexity journey showcases revolutionary technical product management capabilities:

Traditional Paradigm: Technical complexity is an engineering concern requiring engineering expertise to resolve Revolutionary Paradigm: Complexity management is a business capability requiring strategic technical product management with competitive advantage focus

For Technical Product Managers: This represents your competitive advantage—the ability to independently assess technical complexity business impact and lead engineering capability initiatives with competitive positioning benefit.

For Organizations: This methodology provides strategic technical management that translates directly to competitive capability advantage and market differentiation.

The future belongs to product managers who can bridge technical complexity with business value through engineering capability intelligence and strategic technical leadership.



This complexity journey exemplifies how Technical Product Managers can transform routine technical challenges into strategic business capability development that delivers measurable competitive advantage.


After six months of development, VoiceCatcher includes:

  • ~3,000 lines of code across TypeScript, React, and configuration
  • 32 npm scripts handling different build and test scenarios
  • 4 TypeScript configurations for different execution contexts
  • 15+ documentation files providing comprehensive guides
  • Growing test suite with Vitest for reliability
  • 3 build targets: CLI, Electron main process, Electron renderer
  • Multiple execution modes: Development, testing, and production

Appendix: Key Files and Scripts

Essential Scripts

{
  "build": "npm test && npm run build:src && npm run build:main && npm run build:gui",
  "test": "vitest run",
  "test:watch": "vitest",
  "start:gui": "npm run build && electron .",
  "start:gui:fast": "npm run build:fast && electron ."
}

Post-Build Processing

Our custom post-build script handles:

  • Converting ES modules to CommonJS for Electron compatibility
  • Renaming .js files to .cjs extensions
  • Rewriting import paths in compiled files
  • Handling TypeScript definition files

Documentation Structure

docs/
├── build-and-test-process.md
├── technical-design.md
├── project-learnings.md
├── setup-and-installation.md
├── test-driven-refactoring-strategy.md
└── blog/
    └── building-voicecatcher-complexity-journey.md

This blog post serves as both a cautionary tale and a roadmap for others building complex JavaScript applications in 2025. The tools are powerful, but with great power comes great complexity. The key is managing that complexity intentionally rather than letting it emerge chaotically.

Want to explore the VoiceCatcher codebase? Check out our comprehensive documentation and see how we turned complexity into an asset rather than a liability.