fix(map): adaptive node-type filter per connected source protocol (#3610)#3614
Conversation
) The Map Analysis node-type filter exposed five fixed MeshCore-centric categories regardless of which source types were connected. On a Meshtastic-only instance three options (Companion, Room Server, Sensor) were permanently empty and every non-Router Meshtastic node collapsed into a single "Standard" bucket. The filter now adapts to the connected protocols: - Meshtastic-only -> granular Meshtastic role categories (Router, Router Late, Tracker, Sensor, Client Base, etc.), labelled from the shared DEVICE_ROLES map. - MeshCore-only -> the existing MeshCore advType categories. - Both connected -> the union (MeshCore first, then Meshtastic). getNodeTypeCategory now maps each Meshtastic role to its own `mt*` category; map glyphs reuse the MeshCore silhouettes via a new categoryGlyphFamily() so a Router still renders as a repeater tower. The visible category set is derived from the sources in Map Analysis scope (useVisibleNodeTypeCategories), so MeshCore-only surfaces keep their original five categories. Persisted toggles key on stable category names and default to visible, so a stale toggle for a category that drops out of scope is inert and never permanently hides a node when the source mix changes. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011JEaCGwY9Wz8jeV4e22GW4
|
Claude finished @Yeraze's task —— View job PR Review — fix(map): adaptive node-type filter per connected source protocol (#3610)
SummaryThis is a well-structured fix for a real UX problem. The overall approach is sound: a single categorization function drives both the filter UI and map icons, the category set adapts to connected protocols, and the persisted state degrades gracefully when the source mix changes. Here are observations by area. Code Quality
The two-family design ( Minor: // src/utils/nodeTypeCategory.ts:148-174
switch (category) {
case 'mtLostAndFound':
return 'standard';
default:
return 'standard'; // ← unreachable; all 18 union members are covered
}
Potential Bugs1. // src/utils/nodeTypeCategory.ts:199
export function isMeshCoreNode(node: CategorizableNode): boolean {
return !!node.isMeshCore || typeof node.advType === 'number';
}
2. // src/components/MapAnalysis/useVisibleNodeTypeCategories.ts:39
}, [sources, config.sources]);If 3. // src/utils/mapIcons.ts:202-219
} : isRouter ? `
<svg ...>
Pre-#3610, Design / Approach
// src/utils/nodeTypeCategory.ts:256-258
export function sourceTypeProtocol(type: string | undefined | null): 'meshcore' | 'meshtastic' {
return type === 'meshcore' ? 'meshcore' : 'meshtastic';
}This is intentional (unknown type → Meshtastic, the more common case), but a future
The rationale in the comment is correct (while loading, show everything so the control isn't empty), but this means a user whose sources are all disabled (not just loading) also sees the full set rather than an empty/disabled filter. This is the right UX trade-off given persisted-state semantics, but it's worth noting the ambiguity. PerformanceThe SecurityNo security concerns. SVG content rendered via Test CoverageTest coverage is excellent for the utility layer:
Gap: no unit test for Gap: Minor Nits
VerdictSolid, well-scoped fix. The architecture is clean, the tests are thorough for the utility layer, and the graceful-degradation story for persisted state is correct. The |
Problem
Closes #3610.
The Map Analysis node-type filter exposed five fixed MeshCore-centric categories (Companion / Repeater / Room Server / Sensor / Standard) regardless of which source types were connected. On a Meshtastic-only instance this was wrong:
Fix — adaptive categories
The filter (and the analysis legend) now adapt to the protocol families actually in scope:
How it works
getNodeTypeCategory()now maps each Meshtastic device role to its ownmt*category instead of folding intorepeater/standard. Labels reuse the sharedDEVICE_ROLESmap so they match the node list.categoriesForProtocols({ meshcore, meshtastic })computes the ordered visible set;useVisibleNodeTypeCategories()derives the connected protocols from the sources in Map Analysis scope (config.sources, or all enabled sources).categoryGlyphFamily(): Meshtastic ROUTER / ROUTER_LATE / REPEATER draw the repeater tower, SENSOR draws the sensor glyph, client roles fall back to the default pin.roleGlyphInnerSvg/roleGlyphMarkerSvgresolve through it, so the marker a user sees still matches the checkbox.MeshCoreMapand the legacyMapLegendnow iterateMESHCORE_CATEGORIESexplicitly, so the [FEAT] MeshCore node type visualization and filtering on map #3546/feat(map): MeshCore node-type icons + map filtering (#3546) #3563 MeshCore icons/filter behavior is identical.Persisted-state degradation
Toggles still key on stable category names and default to visible when missing. A stale
falsetoggle for a category that drops out of scope (e.g. a MeshCore category after switching to Meshtastic-only) is inert and never permanently hides a node when the source mix changes. Covered by a dedicated test.Tests
nodeTypeCategory.test.ts: Meshtastic role→category mapping (all 13 roles), MeshCore advType mapping,categoriesForProtocolsfor Meshtastic-only / MeshCore-only / both / none,categoryGlyphFamily,sourceTypeProtocol, graceful-degradation case.mapIcons.test.ts: Meshtastic categories render via shared glyph family; client roles return empty glyph.tsc -p tsconfig.server.json --noEmitand frontendtsc --noEmit: no new errors in changed files (only the pre-existing TelemetryChart / 57 frontend errors remain).🤖 Generated with Claude Code