Skip to content

fix(favorites): version-key supportsFavorites cache so it can't stick on unsupported#3482

Merged
Yeraze merged 1 commit into
mainfrom
fix/auto-favorite-firmware-cache
Jun 15, 2026
Merged

fix(favorites): version-key supportsFavorites cache so it can't stick on unsupported#3482
Yeraze merged 1 commit into
mainfrom
fix/auto-favorite-firmware-cache

Conversation

@Yeraze

@Yeraze Yeraze commented Jun 15, 2026

Copy link
Copy Markdown
Owner

Summary

Users on firmware 2.7.24 saw Auto Favorites warn "Firmware 2.7.24 does not support favorites (requires >= 2.7.0)" — a contradiction. The version math is correct (2.7.24 → minor ≥ 7 → supported); the bug is a stale cache. supportsFavorites() cached its result in a bare boolean and cached false whenever it ran before the firmware version was known (localNodeInfo.firmwareVersion starts null until DeviceMetadata arrives). The version then reaches localNodeInfo through NodeInfo/node-rebuild paths (:4307, :4342, :4417) that do not clear favoritesSupportCache — only processDeviceMetadata does. So on reconnect, where the DB already holds the version, the cached false stuck even though firmwareVersion read "2.7.24".

This is the same family as the Traffic Management gate bug (#3457) but a different mechanism — favorites is the one check with a sticky cache; firmwareVersionAtLeast() (Traffic Management / StatusMessage) reads the version fresh each call and is immune.

Changes

  • favoritesSupportCache is now version-keyed ({ version, result }) instead of a bare boolean.
  • supportsFavorites() returns false for unknown firmware without caching it, and serves a cache hit only when it matches the current firmware version — recomputing otherwise. Robust to event ordering and to which path populates the version, without sprinkling cache-clears across the rebuild sites.
  • The disconnect/reset clears (:1616/:1629/:4920) remain valid and unchanged.

Issues Resolved

Resolves the user report: Auto Favorites showing "firmware not new enough" on 2.7.24.

Documentation Updates

No documentation changes needed — internal caching logic.

Testing

  • Full Vitest suite: 7042 tests, 0 failures
  • TypeScript compiles cleanly (tsc --noEmit)
  • New meshtasticManager.favoritesSupport.test.ts (7): 2.7.24 (incl. git suffix), pre-2.7.0, unknown-firmware-not-cached, the poison-then-repopulate regression, version-boundary recompute, and cache-by-version
  • Existing passiveMode and autoFavorite suites still green with the new cache shape

🤖 Generated with Claude Code

… on "unsupported"

Auto Favorites warned "Firmware 2.7.24 does not support favorites (requires
>= 2.7.0)" on firmware that clearly qualifies. The version math is correct;
the bug is a stale cache. supportsFavorites() cached its result in a bare
boolean, and cached `false` whenever it ran before the firmware version was
known (localNodeInfo.firmwareVersion starts null until DeviceMetadata). The
version then reaches localNodeInfo through NodeInfo/node-rebuild paths that do
NOT clear favoritesSupportCache (only processDeviceMetadata does), so on
reconnect — where the DB already has the version — the cached `false` stuck
even though firmwareVersion read "2.7.24".

Fix: key the cache by the firmware version it was computed from, and never
cache the unknown-firmware case. A cache hit is served only when it matches the
current firmware version, so a `false` computed while the version was unknown
is recomputed as soon as the version is populated by any path. Robust to event
ordering without sprinkling cache-clears across the rebuild sites.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@github-actions

github-actions Bot commented Jun 15, 2026

Copy link
Copy Markdown

Claude encountered an error —— View job


I'll analyze this and get back to you.

@github-actions

github-actions Bot commented Jun 15, 2026

Copy link
Copy Markdown

Claude finished @Yeraze's task —— View job


PR Review Complete ✅

Todo List:

  • Read and analyze meshtasticManager.ts changes
  • Review new test file for coverage adequacy
  • Evaluate caching strategy and implementation
  • Check for potential bugs and edge cases
  • Assess performance and security considerations
  • Provide comprehensive review feedback

Code Quality: Excellent 👍

The implementation is clean, well-documented, and follows established patterns in the codebase. The change from a bare boolean cache to a version-keyed cache structure is elegant and solves the root cause rather than applying band-aid fixes.

Key Strengths:

  1. Root Cause Fix: The version-keyed cache ({ version, result }) directly addresses the core issue where false could stick even when firmware version was populated later.

  2. Defensive Programming: The critical fix is on lines 12096-12099 - unknown firmware returns false without caching, preventing the poison scenario.

  3. Comprehensive Test Coverage: The new test file meshtasticManager.favoritesSupport.test.ts covers:

    • Happy path (2.7.24 support)
    • Git-suffixed versions
    • Pre-2.7.0 firmware rejection
    • Critical regression test (lines 67-78) that reproduces the exact bug
    • Cache efficiency validation

Technical Review:

Cache Logic (meshtasticManager.ts:12102-12104):

if (this.favoritesSupportCache?.version === firmwareVersion) {
  return this.favoritesSupportCache.result;
}

Correct: Cache hit only when version matches current firmware, ensuring stale results are impossible.

Edge Case Handling:

  • ✅ Unknown firmware: Returns false without caching (prevents poison)
  • ✅ Unparseable versions: Cached as unsupported for that specific version string
  • ✅ Version changes: Auto-invalidation via version key comparison

Performance Considerations: Good

  • Cache efficiency: String comparison (===) is O(1) for cache hits
  • Minimal overhead: Only parses firmware version on cache misses
  • No memory leaks: Single cache slot, not a growing Map

Security Considerations: No Issues 🔒

  • No user input handling in caching logic
  • Firmware version comes from trusted Meshtastic DeviceMetadata
  • Cache size is bounded (single entry)

Potential Improvements (Minor):

  1. Type Safety: Consider adding an interface for the cache structure:
interface FavoritesSupportCache {
  version: string;
  result: boolean;
}
private favoritesSupportCache: FavoritesSupportCache | null = null;
  1. Edge Case: The current code caches unparseable versions as false (line 12109). This is correct but worth documenting that malformed version strings will be cached.

Test Coverage: Comprehensive 🧪

The test suite covers all critical scenarios including the exact regression from the bug report. The use of mocks is appropriate and the test structure is clean.

Verdict: LGTM

This is a high-quality fix that:

  • Solves the reported bug completely
  • Maintains backward compatibility
  • Includes excellent test coverage
  • Has no performance or security concerns
  • Follows established codebase patterns

The version-keyed caching strategy is robust and will prevent similar issues in the future.


@Yeraze Yeraze merged commit 304ac15 into main Jun 15, 2026
19 of 20 checks passed
@Yeraze Yeraze mentioned this pull request Jun 15, 2026
Yeraze added a commit that referenced this pull request Jun 15, 2026
Bump version to 4.10.4 across package.json, package-lock.json,
helm/meshmonitor/Chart.yaml, desktop/package.json, and
desktop/src-tauri/tauri.conf.json.

Promote CHANGELOG [Unreleased] to [4.10.4] and add the user-facing fixes
merged since 4.10.3: timed-events cross-source firing (#3479), module
config save for Traffic Management/Status Message (#3464), Auto Favorites
firmware-support cache (#3482), MeshCore Share Contact error surfacing
(#3481), and the Firefox-Android delivered-icon CSS fix (#3477). The
air-quality particle-count fix (#3483) was already in [Unreleased].

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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.

1 participant