Dev Diary #30 - Wildhart Jobs, Meteor Docs, Collection2, and Monti APM
Whew, this month has been somewhat dense.
Dense on both a personal and professional level, but thankfully I was able to go on a mini vacation that helped me recharge a bit. I fully intend on doing that again soon, because apparently touching grass every now and then is not only healthy, but also quite useful for keeping one’s brain from melting.
Now back to OSS.
I want to start by refreshing your memory, and also bringing to your attention, that wildhart:jobs finally supports Meteor 3 officially!
wildhart:jobs #31 — Update wildhart to 2.0.0 — This PR has been around for a while, but it finally got merged. The package is now at 2.0.0 and officially supports Meteor 3.
On the Meteor core side, this month had a few documentation-focused contributions.
#14668 — docs: document WebApp.addHtmlAttributeHook — I was able to snatch this little PR off the forums, thanks to Fred and Bertrand.
It is a small documentation change, but a useful one. WebApp.addHtmlAttributeHook existed, but its behavior was not properly documented. The PR added generated API documentation, explained the request object and return contract, documented the merge behavior, and added a simple lang attribute example.
It also corrected the React SSR examples to call WebAppInternals.disableBoilerplateResponse() instead of treating disableBoilerplateResponse like an HTML attribute.
#14669 — docs: validate VitePress theme links — This one came from a suggestion by Samuel/@Greenheart.
Instead of relying on some third-party dead-link checking service, we can directly leverage VitePress and our own docs build to check internal theme links. VitePress already validates links inside Markdown content, but navigation and sidebar links can bypass that. This PR fills that gap.
It validates internal links from the VitePress theme config against the resolved page list, adds dependency-free Node tests, runs those tests in GitHub Actions, and makes sure the checks run before the Netlify docs build.
#14712 — docs: sync generated history with changelog — I noticed that the history page had gone a bit stale and was no longer following Meteor’s latest changes, so I synced it back up.
This regenerated the Meteor 3 history from the canonical changelog entries through 3.5.1, synced the legacy docs/history.md Meteor 3 section with the generated changelog, and preserved the Meteor 2 and earlier history unchanged.
Then there was Collection2.
Collection2 v5.0.0 — Async validation and stronger Zod support — Collection2 got a major release, further enhancing Zod support and modernizing the validation stack.
This one immensely warms my heart.
A while ago, we added multi-schema support to Collection2, allowing it to move beyond its historical hard coupling to SimpleSchema. And now someone is actually using that work and pushing Zod support further, which is exactly the kind of thing I hoped would happen.
Collection2 5 requires Meteor 3.1.2 or newer, SimpleSchema 3 for SimpleSchema users, and Zod 4 for Zod users. It awaits SimpleSchema 3 cleaning, autoValues, field validators, and whole-document validators before database writes. It also adds experimental Zod autoValue support, applies Zod defaults through nested objects and arrays, and tightens validation around modifiers and positional array updates.
In other words, Collection2 is becoming more modern without abandoning its roots.
SimpleSchema remains a first-class compatibility path, but Zod is clearly becoming the premier modern schema option alongside it. Seeing that actually happen in practice is deeply satisfying.
Lastly, I made a contribution that is a little bit out of the ordinary for me.
monti-apm-agent #152 — Track repeated live-query polling pressure — Monti APM is a tool I cherish a lot, and @zodern is an amazing fellow, so I was happy to contribute something useful there.
The story started with a real production pain point.
I had a publication spitting out thousands of documents and causing our app to struggle on load. The existing Monti metrics could tell us about polling work, but they did not make it obvious whether the issue came from one large initial fetch or from the same polling observer repeatedly re-running and diffing a huge result set.
Those are different problems.
A large initial publication can be intentional. Maybe the app genuinely needs to load 12,000 catalog documents once. But if that same publication keeps polling and diffing that large result set every few minutes, then you have recurring database, serialization, and diffing pressure that is much more likely to hurt the app over time.
So I opened a PR that tracks repeated live-query polling pressure.
The detector reuses the document count and approximate byte size already measured after each Mongo poll. It keeps bounded one-minute buckets per polling observer and reports through Monti.trackError only after both repetition and cumulative-document thresholds are crossed. By default, that means at least three polls and at least 25,000 cumulative fetched documents within 15 minutes.
It reports useful context like publication name, collection, polling interval, cumulative and max durations, totals, non-oplog reason, and CPU context. It does not include selectors, publication parameters, document contents, or document IDs.
In other words: useful diagnostics without leaking sensitive app data.
monti-apm/docs #1 — Document repeated live-query polling diagnostics — I also opened a companion docs PR explaining the diagnostic, the default thresholds, configuration, privacy boundary, and remediation path.
The docs distinguish between a large initial fetch and repeatedly polling a large result. They also guide users toward inspecting the non-oplog reason, removing unnecessary disableOplog usage, fixing unsupported selectors or projections, narrowing selectors and fields, and treating longer polling intervals as mitigation rather than the real fix.
As the scouts say: leave the place better than you found it.
Honestly, I should probably add that rule to my previous OSS article. :D