Skip to content

fix: auto-favorite role filter, MQTT notification dot, message text readability#3789

Closed
Yeraze wants to merge 2 commits into
mainfrom
claude/great-dijkstra-s95w4f
Closed

fix: auto-favorite role filter, MQTT notification dot, message text readability#3789
Yeraze wants to merge 2 commits into
mainfrom
claude/great-dijkstra-s95w4f

Conversation

@Yeraze

@Yeraze Yeraze commented Jun 26, 2026

Copy link
Copy Markdown
Owner

Summary

Three bug fixes across two issues.

#3774 — Auto-favorite no longer favorites CLIENT/CLIENT_MUTE on any local role

isAutoFavoriteEligible previously applied the ZERO_HOP_RELAY_ROLES check (ROUTER, ROUTER_LATE, CLIENT_BASE) only when the local node was ROUTER or ROUTER_LATE. For CLIENT_BASE locals, execution fell through to an unconditional return true, auto-favoriting CLIENT_MUTE and CLIENT nodes.

Following the Discord discussion and the official request in the issue, the fix removes the local-role guard — ZERO_HOP_RELAY_ROLES is now enforced for all eligible local roles. This matches the Meshtastic zero-cost hop spec and keeps CLIENT_BASE's selectivity under operator control.

Changed files:

  • src/server/constants/autoFavorite.ts — removed localRole === ROUTER || ROUTER_LATE guard; updated JSDoc
  • src/server/meshtasticManager.autoFavorite.test.ts — corrected CLIENT_BASE + CLIENT → true test to false; added CLIENT_BASE + CLIENT_MUTE → false regression test (28 → 29 tests, all pass)

#3787 Bug 1 — MQTT notification dot now respects "Show MQTT/Bridge Messages"

The unread-counts query counted all messages regardless of viaMqtt. The user's preference was frontend-only and never reached the database.

Fix: excludeMqtt is now threaded through the full stack:

Layer Change
NotificationsRepository.getUnreadCountsByChannelAsync Added excludeMqtt?: boolean; adds Drizzle OR(isNull(viaMqtt), eq(viaMqtt, false)) condition
DatabaseService.getUnreadCountsByChannel (SQLite raw SQL) Added mqttClauseAND (m.viaMqtt IS NULL OR m.viaMqtt = 0)
DatabaseService.getUnreadCountsByChannelAsync Passes excludeMqtt to both SQLite sync and ORM paths
/api/messages/unread-counts endpoint Reads ?excludeMqtt=1 query param
useUnreadCounts hook Accepts excludeMqtt option; includes in URL and TanStack Query key
MessagingContext Reads showMqttMessages from UIContext; passes excludeMqtt: !showMqttMessages
App.tsx Swapped UIProvider/MessagingProvider nesting so MessagingContext can access UIContext (no circular dependency — UIContext has no dependency on MessagingContext)

#3787 Bug 2 — Message time and hop count text is now readable

The time/hop metadata on message bubbles was hard to read due to stacked contrast reductions.

Root cause:

  • .message-time had opacity: 0.7
  • .theirs .message-time used var(--ctp-subtext0) (secondary colour) instead of primary text
  • HopCountDisplay signal and hop-count spans also had opacity: 0.7

Fix:

  • src/styles/messages.css — raised .message-time opacity 0.7 → 0.9; changed .theirs colour to var(--ctp-text); raised .mine opacity 0.7 → 0.9
  • src/components/HopCountDisplay.tsx — signal span 0.7 → 0.9; hop span 0.7 → 1.0

Test plan

  • Auto-favorite: all 29 unit tests pass (npm run test src/server/meshtasticManager.autoFavorite.test.ts)
  • With "Show MQTT/Bridge Messages" unchecked, confirm the Channels notification dot does not appear when only MQTT messages arrive
  • With "Show MQTT/Bridge Messages" checked, confirm the dot does appear for new MQTT messages
  • Confirm message timestamps and hop counts are visually easier to read on both sent and received bubbles

🤖 Generated with Claude Code

https://claude.ai/code/session_01TTF1xow6PNNMeVHHbdvKUR


Generated by Claude Code

… message text readability

**#3774 — Auto-favorite now applies relay-role filter to all eligible local roles**
Remove the CLIENT_BASE exception in `isAutoFavoriteEligible`: ZERO_HOP_RELAY_ROLES
(ROUTER, ROUTER_LATE, CLIENT_BASE) is now enforced regardless of the local node's
role, so CLIENT and CLIENT_MUTE nodes are never auto-favorited.
Adds CLIENT_MUTE regression test; updates the CLIENT test from true → false.

**#3787 Bug 1 — MQTT notification dot respects "Show MQTT/Bridge Messages" pref**
The unread-counts query now supports excludeMqtt, threaded through all layers:
- `NotificationsRepository.getUnreadCountsByChannelAsync` — adds Drizzle OR filter
- `DatabaseService.getUnreadCountsByChannel/Async` — passes through for both SQLite
  (raw SQL `m.viaMqtt IS NULL OR m.viaMqtt = 0`) and ORM paths
- `/api/messages/unread-counts` endpoint — reads `?excludeMqtt=1` query param
- `useUnreadCounts` hook — accepts `excludeMqtt` option, includes in URL + query key
- `MessagingContext` — reads `showMqttMessages` from UIContext (required swapping
  UIProvider/MessagingProvider nesting in App.tsx so MessagingContext has access)
  and passes `excludeMqtt: !showMqttMessages`

**#3787 Bug 2 — Message time/hop text is now readable**
- Raises `.message-time` opacity from 0.7 → 0.9
- Changes `.theirs .message-time` colour from low-contrast `--ctp-subtext0` to
  `--ctp-text` (primary text colour)
- Raises HopCountDisplay signal and hop-count span opacity from 0.7 → 0.9/1.0

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TTF1xow6PNNMeVHHbdvKUR
@github-actions

github-actions Bot commented Jun 26, 2026

Copy link
Copy Markdown

Claude finished @Yeraze's task —— View job


PR Review — fix: auto-favorite role filter, MQTT notification dot, message text readability

Overall: Clean, well-scoped fixes. The changes are coherent with the codebase conventions and the explanations in the PR description are thorough. A few observations below.


Fix 1 — Auto-favorite role filter (src/server/constants/autoFavorite.ts)

Code quality: ✅ Good

The refactor cleanly removes the local-role guard and makes the ZERO_HOP_RELAY_ROLES check apply uniformly. The logic is correct and easy to follow.

Test coverage: ✅ Good

The new tests at lines 22–28 cover the corrected CLIENT_BASE + CLIENT → false and the added CLIENT_BASE + CLIENT_MUTE → false case. The favoriteLocked matrix tests (lines 82–119) are documentation tests — they assert on inline values rather than exercising the actual function, so they add no protection against regressions but don't hurt anything.

Minor observation:

AUTO_FAVORITE_LOCAL_ROLES and ZERO_HOP_RELAY_ROLES are currently identical sets. If this is intentional and the spec says they will always move together, a single constant would reduce the chance of diverging accidentally. If they are intended to diverge in the future (e.g., TRACKER as a local role, but not a relay target), keeping them separate is justified — worth a short comment explaining why.


Fix 2 — MQTT notification dot (src/db/repositories/notifications.ts, src/services/database.ts, src/server/server.ts, src/hooks/useUnreadCounts.ts, src/contexts/MessagingContext.tsx, src/App.tsx)

Architecture: ✅ Correct

The UIProviderMessagingProvider nesting in App.tsx:5418-5419 is the right order so useUI() inside MessagingContext resolves properly.

Database layer (src/services/database.ts:8075-8086):

The mqttClause is appended as an interpolated string, consistent with the surrounding legacy raw-SQL pattern and annotated with the existing ESLint disable comment. However, the clause ordering matters: mqttClause is placed after sourceClause in the template literal but excludeMqtt has no corresponding bind parameter — it's injected as a plain string ('AND (m.viaMqtt IS NULL OR m.viaMqtt = 0)'), not a parameterized value. This is safe here because mqttClause contains no user-controlled data (it's either an empty string or a hardcoded literal), but it's worth calling out so future maintainers don't accidentally inline user input using the same pattern.

ORM layer (src/db/repositories/notifications.ts:850-851):

conditions.push(or(isNull(messages.viaMqtt), eq(messages.viaMqtt, false)));

For PostgreSQL and MySQL, viaMqtt is a boolean column. eq(messages.viaMqtt, false) is correct. For SQLite however, the sync path (getUnreadCountsByChannel) is used instead of this Drizzle query, so there is no cross-path inconsistency risk.

Potential gap — DM unread counts:

excludeMqtt is applied to channel unread counts (getUnreadCountsByChannelAsync) but not to DM unread counts (getBatchUnreadDMCountsAsync at server.ts:2659). If MQTT DMs are possible and the user hides MQTT messages, DM badges could still light up. This may be intentional (MQTT DMs are rare/non-existent in practice) but is worth confirming.

useUnreadCounts query key (src/hooks/useUnreadCounts.ts:66):

queryKey: ['unreadCounts', baseUrl, sourceId, excludeMqtt],

excludeMqtt is correctly included in the query key, so toggling the setting invalidates the cache and refetches. ✅

MessagingContext reactivity:

showMqttMessages is read from useUI() and passed as excludeMqtt: !showMqttMessages. When the user toggles this setting, TanStack Query will re-run the query because excludeMqtt is in the query key. However, the legacy unreadCounts state (line 56) is only updated via fetchUnreadCounts (the manual refetch wrapper). If any consumer reads unreadCounts (the legacy state) rather than unreadCountsData, it will lag behind the toggle until a manual refetch. This looks like a pre-existing issue rather than a regression from this PR.


Fix 3 — Message readability (src/styles/messages.css, src/components/HopCountDisplay.tsx)

CSS changes: ✅ Correct

  • .message-bubble .message-time: opacity: 0.9 is better than 0.7.
  • .message-bubble.theirs .message-time: switching from var(--ctp-subtext0) to var(--ctp-text) gives proper contrast against the received bubble background.
  • .message-bubble.mine .message-time: opacity: 0.9 consistent with the theirs rule.

HopCountDisplay (src/components/HopCountDisplay.tsx:94,106):

  • Signal span: 0.9 opacity ✅
  • Hop span: 1.0 (explicit full opacity) ✅

No issues here. The fix is minimal and correct.


Summary

Area Status
Auto-favorite logic ✅ Correct
Auto-favorite tests ✅ Adequate (minor: doc-only tests in favoriteLocked matrix)
MQTT notification dot — DB/ORM ✅ Correct; mqttClause string injection is safe but worth a note
MQTT notification dot — DM counts not filtered ⚠️ Possible gap (DMs not excluded from unread badge when MQTT hidden)
MQTT notification dot — query key cache invalidation ✅ Correct
Message readability ✅ Correct

The DM-count gap is the only item worth a follow-up check. Everything else looks solid.

Adds the lxc/ directory to sparse-cone.txt so LXC template builds include
build-lxc-template.sh and sparse-cone.txt itself (required at build time).
Also converts CRLF → LF and adds missing trailing newline.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TTF1xow6PNNMeVHHbdvKUR
@Yeraze

Yeraze commented Jun 26, 2026

Copy link
Copy Markdown
Owner Author

Closing as redundant after untangling overlapping PRs on #3774/#3787/#3782:

No unique work is lost — both underlying issues are covered by #3786 and #3790. Closing to keep one focused PR per issue.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants