fix: restore unread badge on Rocket.Chat 7.8.0+ servers - #3369
Conversation
The dock/tray/sidebar unread badge stopped appearing on servers running Rocket.Chat 7.8.0 or newer. Server PR RocketChat/Rocket.Chat#36001 ("refactor: remove `unread` from Meteor", first shipped in 7.8.0) deleted `client/startup/unread.ts`, which was the only code writing `Session.set('unread', ...)`. Unread state moved to a React-only store. The desktop app reads the badge exclusively through `Tracker.autorun(() => setBadge(Session.get('unread')))` in injected.ts, so on those servers that Meteor source now resolves to `undefined` forever and the badge clears across every sink. Those servers still broadcast unread state through two global CustomEvents on `window`, mirroring the server's own `useUnread` hook: - `unread-changed`: the aggregate numeric count (recompute trigger). - `unread-changed-by-subscription`: per-subscription { rid, unread, alert, unreadAlert }, accumulated here to rebuild the alert-only "•" indicator. injected.ts now listens to both and resolves the badge with the same logic useUnread uses: positive count wins, otherwise alert-only "•", otherwise no badge. The legacy Session autorun is kept as a fallback for servers older than 7.8.0. Known limitation: `unread-changed-by-subscription` is incremental, so an alert-only room (zero count) that settled before the listener attached may not show its dot until it next changes; any real unread count always shows immediately.
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
📜 Recent review details⏰ Context from checks skipped due to timeout. (8)
Walkthrough
ChangesUnread Badge Event-Based Tracking
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related issues
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Warning There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure. 🔧 ESLint
ESLint install failed. For unrecoverable errors, disable the tool in CodeRabbit configuration. Warning Review ran into problems🔥 ProblemsErrors were encountered while retrieving linked issues. Errors (1)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/injected.ts`:
- Around line 423-440: The Tracker.autorun block that handles the legacy Meteor
Session reactive dict for unread badges is not gated on server version, allowing
it to run on 7.8.0+ servers and call setBadge(undefined), clearing the badge
value restored by the event-based approach. Wrap the Tracker.autorun block with
a version check to ensure it only executes on pre-7.8.0 servers, preventing the
legacy path from interfering with the newer event-based badge mechanism on
7.8.0+ servers.
- Around line 441-495: The unread badge count is being calculated by summing
only the subscriptions in the unreadSubscriptions map, which misses any rooms
that had unread counts before the event listeners were attached. The
'unread-changed' event listener on line 492 receives the complete aggregate
count but discards it and just calls resolveBadge() which recalculates from the
incomplete map. Modify the 'unread-changed' event listener to extract the
aggregate unread count from the event detail and use that directly instead of
calculating it from the map entries, either by passing the count to
resolveBadge() or by setting the badge directly with the received aggregate
value.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 541de790-d119-4173-a13d-1409d17e16b7
📒 Files selected for processing (1)
src/injected.ts
📜 Review details
⏰ Context from checks skipped due to timeout. (4)
- GitHub Check: check (ubuntu-latest)
- GitHub Check: check (windows-latest)
- GitHub Check: build (windows-latest, windows)
- GitHub Check: build (ubuntu-latest, linux)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.ts
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.ts: Use TypeScript for all new code unless explicitly told otherwise
Use optional chaining with fallbacks for platform-specific APIs instead of mocking when possible. Example:const uid = process.getuid?.() ?? 1000;
Files:
src/injected.ts
**/*.{tsx,ts}
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.{tsx,ts}: MANDATORY: Use Fuselage components for all UI work. Only create custom components when Fuselage doesn't provide what's needed
Import UI components from@rocket.chat/fuselageand checkTheme.d.tsfor valid color tokens
Use React functional components with hooks
Use PascalCase for component file names
Files:
src/injected.ts
**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.{ts,tsx}: Redux actions must follow FSA (Flux Standard Action) pattern
Avoid unnecessary comments — write self-documenting code through clear naming
Always verify libraries by checking official docs and.d.tsfiles innode_modules/. Never assume props, tokens, or APIs work without verification
Avoid subjective descriptors ('smart', 'excellent', 'dumb') in documentation and comments
Use measurable descriptions in code documentation: 'reduced memory usage', 'improved by X%' instead of subjective claims
NEVER invent metrics — don't include estimated time spent or speculated user counts. Only include numbers from actual logs, error messages, or documented sources
Files:
src/injected.ts
🔇 Additional comments (1)
src/injected.ts (1)
386-386: LGTM!Also applies to: 398-406
Linux installer download |
macOS installer download |
Address CodeRabbit review on PR #3369: - Gate the legacy Session.get('unread') autorun behind a pre-7.8.0 version check so it no longer fires setBadge(undefined) on 7.8.0+ servers and clobbers the event-based badge. - Use the authoritative aggregate count carried by the 'unread-changed' event instead of the incremental per-subscription map, which could undercount rooms unread before the listeners attached.
Satisfy prefer-destructuring lint rule.
The per-server sidebar tile rendered a literal "0" overlay whenever a server's unread badge was the number 0. PR #3369 (restore unread badge on 7.8.0+ servers) made injected.ts emit setBadge(0) explicitly via `setBadge(alertIndicator ?? 0)`, so servers now carry `badge: 0` where they previously carried `undefined`. SideBar/index.tsx maps that to `mentionCount={0}`, and ServerButton guarded the Badge with `{mentionCount && <Badge>...}`. Because `0 && <Badge>` evaluates to `0`, React renders the number as a text node — the stray "0" on the tile. Coerce the guard to a boolean (`!!mentionCount`) so a zero count renders nothing. Add a regression test asserting mentionCount={0} produces no "0" text. The aggregate badge sinks (selectGlobalBadge, tray title, dock) already collapse an all-zero state to undefined, so only the per-server tile was affected and they are left unchanged.
Proposed changes
Restores the unread badge (dock, tray, and sidebar server avatar) on servers running Rocket.Chat 7.8.0 or newer, where it had stopped appearing.
Root cause
The desktop app reads the unread badge exclusively from Meteor's reactive
Sessiondict, viainjected.ts:Server PR RocketChat/Rocket.Chat#36001 — "refactor: remove
unreadfrom Meteor" (ARCH-1606), first shipped in 7.8.0 — deletedapps/meteor/client/startup/unread.ts, the only code that wroteSession.set('unread', …). Unread state moved to a React-only store (useUnreadhook). On those servers,Session.get('unread')resolves toundefined, sosetBadge(undefined)clears the badge across every sink. The desktop code itself was unchanged, so nothing in the desktop changelog pointed at the cause; the break surfaces only as each server independently upgrades past 7.8.0.Fix
Those servers still broadcast unread state through two global
CustomEvents onwindow(dispatched unconditionally byfireGlobalEventBase), mirroring the server's ownuseUnreadhook:unread-changedunread-changed-by-subscription{ rid, unread, alert, unreadAlert }•indicatorinjected.tsnow listens to both and resolves the badge with the same logicuseUnreaduses: a positive count wins, otherwise an alert-only•, otherwise no badge. TheunreadAlertuser preference is read fromMeteor.user()when available and defaults totrue(the server's shipped default).The legacy
Session.get('unread')autorun is kept as a fallback for servers older than 7.8.0, so the badge works across both old and new servers.Known limitation
unread-changed-by-subscriptionis incremental (fires only when a subscription changes). An alert-only room (zero unread count) that settled before the listener attached may not show its•dot until it next changes. Any real unread count always shows immediately. A future server-side change adding the alert state to a global event would remove this edge.Steps to test or reproduce
Summary by CodeRabbit