Shipping vTimers: what actually broke between the idea and the deploy

vTimers is a set of free online timers. No account, no tracking. It has run for years on a small cloud VM, and this week I spent a day making it something I would be happy to show someone.

The interesting part is not the features. It is the three bugs, none of which were in the code I was working on.

The constraint

Next.js in a container on a small cloud server with a gigabyte of RAM. You do not build on that. You build locally and pipe the image over.

The box had been up since 2023, with two containers Exited for three years and 1.1 GB of unreferenced images. Pruning reclaimed 2.1 GB.

The migration that recoloured everything

I upgraded Tailwind 3 to 4 and daisyUI 4 to 5. Build passed. Lint passed. Tests passed. Then I looked at the site and the primary colour was purple. It had been red and black for years.

The migration had registered daisyUI's stock light theme, which sets a purple primary on [data-theme=light]. My red lived in :root. Both were in the stylesheet, and daisyUI won, because its variables sit inside :is() selectors and carry higher specificity.

A passing build tells you nothing about visual correctness.

The same bug had a second half. daisyUI 5 switches themes on a [data-theme] attribute. My provider toggled a .dark class. Two mechanisms, never connected, so every component stayed light while the page went dark.

The fix was to stop registering their palettes and declare one custom theme delegating to my own variables. Because those change under .dark, one theme produces both modes.

The header that broke a clock

The console showed a Content-Security-Policy violation on load. One component fetched a locale list, and connect-src did not include that host. Blocked on every page load, for every visitor, silently.

I had added that CSP myself as hardening. It was doing its job, and it was also breaking a feature. A CSP is a functional change, not just a security one, and the failure mode is silence.

Two clocks, stacked

The homepage rendered the same component twice, one above the other, both ticking. It survived a build, a test suite, and my own review, because I was looking at what I had changed rather than at the page.

What I would do differently

Write the browser test before the fix. I ended up with 23 checks, and three caught bugs I had already shipped.

Assert on computed values, not source. "The CSS declares red" was true the entire time the site was purple.

All three bugs were in code I had not touched, and all three were invisible to every check I had. The features worked. The product was wrong. Closing that gap takes a browser and the willingness to look at the screen instead of the diff.

← All writing