Test-user report: the nav unread-thread badge was stuck at 99+, "mark all read" didn't clear it, and hitting the New posts button said "all caught up". Three unread scopes had drifted apart.
| Path | Scope |
|---|---|
BaseController nav badge | f.visibility <> 'hidden' — counts private forums the user isn't a member of, plus threads in mod-only categories |
/forums/new (showAllUnread) | Forum::allGrouped($role, $uid) — respects category min_role_view + forum membership |
/forums/mark-all-read | Same as /forums/new — Forum::allGrouped-scoped |
So the badge counted threads the user could neither read nor mark. Click mark-all-read → marked everything in the correct scope → /forums/new became empty → but the badge recomputed from its broader scope and stayed inflated. Spot check on @orion (role 2) while debugging:
old_badge_scope 485 threads
new_badge_scope 0 threads
Every one of those 485 was in a private forum orion isn't a member of, or a mod-only category orion can't see. Classic scope-mismatch bug that the nav caching made worse by pinning the stale number for 30s.
Fix
- New
ForumThread::unreadTotalForForums(int $userId, array $forumIds): int— same unread predicate as the existingunreadByForumIds/unreadCountsByForums, just returns a single count restricted to the supplied forum IDs. BaseControllernow computes the badge viaForum::allGroupedfirst (same call as /forums/new + mark-all-read), then passes those forum IDs to the new total helper. All three paths are locked to the same scope — drift can't happen again.- Proactive cache flush of every
nav_unread_count_*.jsonfile instorage/cacheso the scope change takes effect on the very next page-load instead of waiting 30 seconds for the TTL.
Tradeoff: the nav badge now does two queries instead of one (allGrouped + unread total) on a cache miss. Runs once per 30 seconds per user, so aggregate cost is unchanged. Gains correctness in exchange.
. __ ____ ___ ____ _ _
/ /_| ___| / _ \___ \(_)___| |__
| '_ \___ \| | | |__) | / __| '_ \
| (_) |__) | |_| / __/| \__ \ | | |
\___/____/ \___/_____|_|___/_| |_|
D2sk - Sysop