Date: 2026-03-11 Severity: High — core user interactions broken on production web app Time to Resolution: ~6 hours of investigation
A 3-year-dormant npm package (vue-virtual-scroller) published a new beta version (2.0.0-beta.9) on 2026-03-10. Due to a combination of semver caret ranges and a CI pipeline that deletes package-lock.json before every build, the new version was silently picked up on the next deployment. The new version is a complete ESM module rewrite that broke the DynamicScroller component, causing two major symptoms:
- Feed flickering on scroll — virtual list recycling broke, causing visible re-renders
- Comment textbox not opening — clicking the comment icon on feed/profile pages had no visible effect; component state was being lost during virtual DOM recycling
| Time | Event |
|---|---|
| 2026-03-10 ~13:59 UTC | vue-virtual-scroller@2.0.0-beta.9 published to npm (first release in 3 years) |
| 2026-03-10 | CI build triggered — package-lock.json deleted, npm install resolves beta.9 |
| 2026-03-10 | Deployment goes live with broken dependency |
| 2026-03-11 | Bug reports: comment icon not working, feed flickering |
| 2026-03-11 ~14:00 | Investigation begins — initially focused on Vue component logic, CSS, nginx config, Cloudflare caching |
| 2026-03-11 ~17:30 | Breakthrough: compared working deployment (2026-03-07) vs broken deployment (2026-03-10) CI logs |
| 2026-03-11 ~17:45 | Root cause identified — npm install output shows vue-virtual-scroller@2.0.0-beta.9 in broken build vs 2.0.0-beta.8 in working build |
| 2026-03-11 ~18:00 | Fix PR created — pin exact version 2.0.0-beta.8 |
| 2026-03-11 ~18:45 | Fix verified locally (beta.9 reproduces bug, beta.8 resolves it) |
| 2026-03-11 ~19:00 | Fix deployed to development environment, confirmed working |
"vue-virtual-scroller": "^2.0.0-beta.8"The caret (^) allows npm to install any version >=2.0.0-beta.8 <3.0.0. This is standard practice for stable packages but dangerous for beta/pre-release dependencies where breaking changes are expected between versions.
The CI build script (build-and-deploy.yml) runs:
rm -rf node_modules package-lock.json
npm installThis forces fresh dependency resolution on every build. Without a lock file, npm resolves the latest compatible version per semver rules. When beta.9 appeared on the registry, the next build picked it up automatically.
vue-virtual-scroller@2.0.0-beta.9 is a complete rewrite:
- Converted from CommonJS to ESM modules
- Rewrote the
DynamicScrollercomponent internals - Changed how virtual DOM recycling and item state management works
The DynamicScroller component is used in FeedTemplate.vue to render the main content feed. The rewrite broke:
- Item state preservation — component state (like
commentAccordiontoggle) was lost during virtual scroll recycling, so clicking the comment icon appeared to do nothing - Scroll stability — items would flicker/re-render during scroll as the recycling logic behaved differently
Local development machines had node_modules/vue-virtual-scroller already installed at 2.0.0-beta.8 from a previous npm install. Since developers don't delete node_modules and package-lock.json before every install, npm saw the existing version satisfied the semver range and kept it. The bug was only reproducible by:
- Deleting
node_modulesand runningnpm install(which would pull beta.9), or - Explicitly installing beta.9:
npm install vue-virtual-scroller@2.0.0-beta.9
This created a frustrating gap where everything worked locally but was broken in the deployed environment.
PR: Pin vue-virtual-scroller to an exact version (remove caret):
- "vue-virtual-scroller": "^2.0.0-beta.8"
+ "vue-virtual-scroller": "2.0.0-beta.8"This single-character change prevents npm from auto-upgrading to any future versions.
Before identifying the root cause, several hours were spent investigating:
- Vue component logic —
UserEngagement.vue,PostDetails.vue,CreatePostPopup.vue— all correct - API layer — verified comments API worked fine via curl
- Nginx configuration — found and fixed an unrelated PWA file-serving issue, but it didn't resolve the symptoms
- Cloudflare caching — investigated cache rules, purged cache — not the issue
- CSS/styling — reviewed scoped styles, no conflicts found
- Event tracking library — diffed
vue-event-trackingv2.0.0 vs v2.0.1 — only added payload fields, no click interception
The breakthrough came from directly comparing CI build logs between a known-good deployment and the broken deployment. The npm install output clearly showed different resolved versions of vue-virtual-scroller.
The lock file exists to guarantee reproducible builds. Deleting it defeats this purpose and exposes every build to upstream changes. The CI script should be changed from:
rm -rf node_modules package-lock.json
npm installTo:
npm cinpm ci uses the lock file for deterministic installs and is faster (it deletes node_modules itself and installs from scratch based on the lock file).
Semver caret ranges are reasonable for stable packages that follow semver conventions. But beta/pre-release packages can (and do) introduce breaking changes between versions. Always pin exact versions for pre-release dependencies:
"some-beta-package": "2.0.0-beta.8" // ✅ exact
"some-beta-package": "^2.0.0-beta.8" // ❌ allows upgradesWhen local works but deployed doesn't, the first step should be comparing the exact build artifacts — CI logs, dependency trees, bundle contents. This would have saved several hours of investigating application code that was never the problem.
A package with no releases for 3 years suddenly publishing a major rewrite is a red flag. Consider:
- Forking critical dependencies that are unmaintained
- Using
npm auditand dependency monitoring tools - Setting up alerts for unexpected dependency changes
- Package: vue-virtual-scroller
- Maintainer: Guillaume Chau (Akryum), Vue.js core team member
- beta.8 published: 2023-02-06
- beta.9 published: 2026-03-10 (1,128 days later)
- Change: Complete ESM rewrite of the virtual scrolling library