The Endless Debugging

Introduction

Before I began building Progressive Web Apps (PWAs) with the help of artificial intelligence, I believed I had a clear vision of what an ideal solution should look like.
  • If I were fluent in programming, I could have implemented clean, bug-free solutions that precisely addressed user needs.
  • In my view, a well-designed app should simplify workflows, rather than introduce friction or impose extra steps that do not align with real-world usage.
  • However, in practice, developers may not fully understand the actual workflows, while users may not articulate their needs clearly or may passively accept whatever is given.

In hindsight, I realised that my idealism about building a “perfect” app stemmed from a limited understanding of the technical foundations required to support such ambitions.



What Users Take for Granted

As users, we have grown accustomed to feature-rich, polished applications that just work.

  • For example, a professional PDF editor is expected to support text highlighting, annotations, typewriter input, shape and image insertion and advanced tools like file compression, page merging/splitting, OCR, and format conversion (e.g., to Word).
  • We often take these features for granted, unaware of the immense engineering effort and edge-case handling required to deliver them reliably across platforms.

When I began developing an eBook reader PWA with offline text-to-speech (TTS) functionality using the Web Speech API, I quickly encountered many of these hidden challenges.

  • For example, pdf.js renders PDF pages onto <canvas> elements, which means annotations and text selections are not natively supported and must be overlaid as separate layers. This makes implementing features like highlighting, selection-based TTS, or annotation syncing far more complex in a client-side environment.
  • As I worked on offline rendering for .txt, .epub, and .pdf formats, I ran into persistent issues of frequent re-rendering that made the process feel overwhelming.
However, approaching app design incrementally (i.e. introducing one feature at a time and refining interactions through real usage) helped the project gradually evolve into a production-ready state.

  • Each layer of functionality needed careful consideration.
  • For example, implementing TTS was not just about adding play and stop buttons. It required controls for volume, pitch, and speech rate, thoughtful integration with content formats like EPUB's continuous scrolling mode, and even voice engine selection tailored to the user’s language preferences.

I recall once suggesting a drill-down calendar navigation interface for easier long-range date selection in a mobile app.
  • The developer responded that it was not feasible and instead implemented a jump-to-month alternative.
  • From a user’s perspective, it felt like a small improvement.
  • However, from a technical standpoint, such changes can be surprisingly complex to implement within existing UI constraints, data models, or third-party library limitations.



AI Assistance in Action

There is no question that AI has made it far easier to prototype and even build full-featured applications.
  • I have created several PWAs simply by prompting AI, often without a deep understanding of every line of code it generated.
  • But while AI can accelerate initial development, it does not eliminate and sometimes even obscures the complexity of real-world debugging.
Even with AI’s assistance, app development often takes days, not hours, especially when you hit an unresolvable issue.
  • You will encounter subtle React hook pitfalls, such as useEffect firing unexpectedly due to a missing dependency in its array.
  • You will face race conditions, where multiple Promise.all calls resolve in an unpredictable order, corrupting your application state.
  • You will debug unhandled promise rejections and hunt down the source of infinite re-renders caused by state being updated within a render cycle.
  • To identify the underlying cause, you will need to insert visual indicators, console.log() statements, and sometimes even prompt AI to explain the logic it generated just to understand your app’s behavior.
  • The worst experience is when an error is reported in one script, and the AI offers a fix for that script, but the root cause is actually an entirely different parent component.
A particularly difficult case was implementing offline support for my rxlookup PWA. The app used URL-based navigation to display individual drug entries. Making this work offline required a complex interplay of systems:
  • Properly registering the service worker via sw.js.
  • Ensuring that IndexedDB loaded and hydrated app data.
  • Pre-caching Next.js static assets.
  • Serving an App Shell for unvisited routes.
  • Dynamically handling route changes based on drug ID.
The major headache came when AI attempted to integrate next-pwa and Workbox using an injectManifest strategy, while also trying to serve offline.html for fallback navigation.
  • It was my first PWA project, and I lacked a clear conceptual model of how offline support actually works beyond simply "adding a service worker and a manifest."

At one point, I spent an entire day trying to troubleshoot the same unresolved issue, despite AI-generated suggestions.

  • The mental fatigue started to erode my confidence, and I began to wonder if I should have hired a professional to implement it properly from the beginning.

Eventually, repeated sw.js errors in the console helped me realize that a misconfigured service worker would break the offline experience entirely, like a car breaking down in the middle of a journey.

  • But that realization only came after days of fruitless debugging.
In my most recent eBook reader project, this challenge re-emerged.
  • The AI, with overconfidence, implemented the Text-to-Speech (TTS) functionality using Google's new genkit. It did this without asking my plan.



Beyond the Surface

After completing a functional prototype with basic features, the app will undergo further refinement through a multi-faceted approach. This will include

  • Enhancing the user experience (e.g., implementing lazy loading for .txt and .pdf files to efficiently handle large documents within the eBook reader, implementing debounce logic for a smoother search experience),
  • Optimizing performance on mobile devices (e.g., improving responsive layouts for various screen sizes and refining button interactions), and
  • Strengthening security (e.g., configuring a Content Security Policy and implementing appropriate security headers).

Solving obvious bugs is only the beginning.

  • What comes next is a slow, iterative process of fine-tuning, uncovering edge cases, and validating how the app behaves under real-world conditions.
  • Local development and preview modes often give the illusion that everything is working correctly
  • However, it is only through extended use across different devices, network conditions, and user scenarios that deeper issues begin to surface.
  • For example, in my RxBudget PWA, I initially believed I had sufficiently guided the AI to implement support for recurring income entries, such as monthly salaries.
  • However, during real usage, I discovered that entries with an undefined endDate (an optional field in budget planning) triggered Firestore synchronization errors.
  • During real-world testing of the budgeting app, I also identified a recurring hidden issue: transactions being assigned to the incorrect date due to time zone discrepancies.
  • Another example is although I have implemented the create, edit, and delete functions for bookmarks in the ebook reader PWA, I later realized that I forgot to import the embedded bookmarks from the PDF.
  • This issues went unnoticed in local testing as the edge case had not been triggered in test scenarios.

These types of silent failures underscore the need for rigorous end-to-end validation, not just "happy path" checks.



Summary

Technology evolves rapidly.

  • Challenges that once required custom logic are now abstracted away by well-maintained libraries.
  • I am deeply grateful to the open-source community for their contributions, which enable developers to focus more on architecture and less on low-level details.

That said, fast-paced innovation comes with trade-offs.

  • Keeping apps compatible across library versions and browser environments is a constant challenge.
  • Choosing to upgrade to the latest version of dependencies might introduce breaking changes, requiring a partial or complete codebase refactor.
  • On the other hand, sticking with outdated versions can lead to security vulnerabilities or poor cross-browser support.

Ultimately, technical decision-making is a balance between long-term maintainability and short-term stability.

  • And the more you build, the more you appreciate that well-functioning software is not just code.
  • It is architecture, process, and a lot of trial and error.
  • Any overlooked detail, no matter how small, can become a latent bug if it is not anticipated and handled up front.

In app development, the struggle to perfect an app feels more real than any theoretical framework.

  • The key is simple: persistence, paired with gratitude for small wins along the way.

Comments