Skip to content

feat(meshcore): add Telemetry dashboard tab via source-agnostic Dashboard#3142

Merged
Yeraze merged 1 commit into
mainfrom
feature/3139-meshcore-telemetry-dashboard
May 22, 2026
Merged

feat(meshcore): add Telemetry dashboard tab via source-agnostic Dashboard#3142
Yeraze merged 1 commit into
mainfrom
feature/3139-meshcore-telemetry-dashboard

Conversation

@Yeraze

@Yeraze Yeraze commented May 22, 2026

Copy link
Copy Markdown
Owner

Summary

Closes part 2 of #3139. Adds a destination for MeshCore favorited-telemetry charts. Until this PR, the favorite-star toggle was present in MeshCore views (DM contact panels) but no MeshCore-side surface read `telemetryFavorites` back — the user could star a chart but couldn't see it anywhere afterward.

What changed

1. Dashboard becomes source-agnostic

New `DashboardDataSource` abstraction (`src/components/Dashboard/dataSources.ts`). Every Meshtastic-specific assumption inside the per-source `Dashboard` now goes through the adapter:

Concern Meshtastic adapter MeshCore adapter
Nodes endpoint `/api/nodes` `/api/sources/:id/meshcore/nodes`
Node-key extraction `user.id` `publicKey` (via `user.id` after shape adaption)
Display name `user.longName || user.shortName` same (adapter populates these from `node.name`)
Role label `getDeviceRoleName(role)` `advType` → "Companion" / "Repeater" / "Room Server"
Solar default-on types Meshtastic-flavoured names `mc_battery_volts_ch1..ch4`, env sensors
Custom widgets shown hidden — `nodeStatus` / `traceroute` / `hopDistribution` widgets all assume Meshtastic shapes

Two adapters ship: `meshtasticDashboardSource` (default, exactly preserves prior behaviour) and `meshcoreDashboardSource`. The Dashboard component accepts the adapter as an optional prop with the Meshtastic default, so the existing `App.tsx` mount point at `activeTab === 'dashboard'` is unchanged.

2. New MeshCore Telemetry view

  • New `MeshCoreTelemetryView` wrapper mounts ``.
  • New `'telemetry'` entry on `MeshCoreView` and a 📊 Telemetry button on `MeshCoreSubToolbar` (between DMs and Node Info).
  • `MeshCorePage` renders the new view when `view === 'telemetry'`.

3. `mc_*` labels in `TELEMETRY_LABELS`

Adds entries for every LPP type in `LPP_TYPE_NAMES` and every `mc_status_*` field in `STATUS_FIELD_MAP`. The label resolver also recognises the `_ch` suffix from #3141 and renders e.g. `mc_battery_volts_ch2` as `Battery (ch2)` — so we don't need to enumerate every type×channel combination in the label table.

Test plan

  • `npx tsc --noEmit` clean
  • Full Vitest suite: 5411 / 5411 passing (5395 before + 16 new tests in this PR)
  • New `dataSources.test.ts` covers MeshCore adapter shape mapping, role labels, key extraction, per-source defaults
  • New `TelemetryChart.test.ts` covers channel-suffix label resolution
  • Manual: navigate into a MeshCore source → click 📊 Telemetry → verify favorited charts render and "Add Widget" button is hidden

Notable design decisions

  • No data-shape forking inside Dashboard internals. The MeshCore adapter normalises into the existing `NodeInfo` shape at fetch time, so hooks and grid don't have to know which source they're rendering for. Keeps the diff small (≈550 lines, mostly new files).
  • Custom widgets are gated, not duplicated. Rather than build a separate `MeshCoreDashboard` component, the four Meshtastic-only widget kinds are hidden via `showCustomWidgets: false`. If/when MeshCore grows equivalent widgets, they add to the same flag-gated `AddWidgetModal` flow.
  • Favorites storage is global. `telemetryFavorites` is shared across sources, keyed on `(nodeId, telemetryType)`. A user who'd already starred MeshCore charts (in DM contact panels) before this PR ships will see those favorites appear on the new tab automatically.

Builds on #3141 (LPP channel byte preservation), merged earlier.

🤖 Generated with Claude Code

…oard (#3139)

Builds out the missing destination for "favorited" MeshCore telemetry
charts. The favorite-star toggle was already present in MeshCore views
(DM contact panels), but there was no MeshCore-side surface that read
`telemetryFavorites` back. Reported by @HougeDK in #3139.

## What changed

1. **Dashboard becomes source-agnostic** via a new `DashboardDataSource`
   abstraction (`src/components/Dashboard/dataSources.ts`). Every
   Meshtastic-specific assumption now goes through the data-source
   adapter:
   - Nodes endpoint (`/api/nodes` vs `/api/sources/:id/meshcore/nodes`)
   - Node-key extraction (`user.id` vs `publicKey`)
   - Device-role labelling (`getDeviceRoleName` vs MeshCore advType map)
   - Solar-overlay default-on type list
   - Whether the "Add Widget" button is shown (MeshCore: false, since
     `nodeStatus` / `traceroute` / `hopDistribution` widgets all assume
     Meshtastic data shapes and protocol concepts)

   Two adapters ship: `meshtasticDashboardSource` (the default, exactly
   preserves prior behaviour byte-for-byte) and `meshcoreDashboardSource`
   (the new one). The Dashboard component accepts the adapter as an
   optional prop with the Meshtastic default, so the existing App.tsx
   mount point is unchanged.

2. **New `MeshCoreTelemetryView`** mounts the same `<Dashboard>` with
   `meshcoreDashboardSource`. Adds a "📊 Telemetry" entry to
   `MeshCoreSubToolbar` between Direct Messages and Node Info.

3. **`mc_*` label support in `TELEMETRY_LABELS`** — adds entries for
   every LPP type name from `LPP_TYPE_NAMES` and every `mc_status_*`
   field from `STATUS_FIELD_MAP`. The label resolver also recognises
   the `_ch<N>` suffix from #3141 and renders e.g. `mc_battery_volts_ch2`
   as `Battery (ch2)` without enumerating every type×channel combo.

## Test plan

- [x] `npx tsc --noEmit` clean
- [x] Full Vitest suite: 5411 / 5411 passing (5395 before + 16 new)
- [x] New `dataSources.test.ts` covers the MeshCore adapter shape mapping,
      role labels, key extraction, and per-source defaults
- [x] New `TelemetryChart.test.ts` covers the channel-suffix label resolution
- [ ] Manual smoke: open a MeshCore source, navigate to the Telemetry tab,
      verify favorited charts render and the "Add Widget" button is hidden

## Notable design decisions

- **No data-shape forking inside Dashboard internals.** The adapter
  normalises both sources into the existing `NodeInfo` shape at fetch
  time, so the hooks and grid don't have to know which kind of node
  they're holding. Keeps the diff small.
- **Custom widgets are gated, not duplicated.** Rather than build a
  separate `MeshCoreDashboard` component, we hide the four
  Meshtastic-only widget types via `showCustomWidgets: false` on the
  MeshCore adapter. If/when MeshCore grows equivalent widgets (e.g.
  MeshCore-flavoured hop distribution), they get added to the same
  flag-gated AddWidgetModal flow.

Closes part 2 of #3139.

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

github-actions Bot commented May 22, 2026

Copy link
Copy Markdown

Claude finished @Yeraze's task —— View job


Code Review: MeshCore Telemetry Dashboard Feature

This is an excellent implementation that successfully adds source-agnostic dashboard functionality to MeshMonitor. The architecture is well-designed and the code quality is high. Here's my detailed analysis:

Architecture & Design Excellence

Source-Agnostic Abstraction: The DashboardDataSource interface (src/components/Dashboard/dataSources.ts:24-48) is a textbook example of the adapter pattern. Clean separation of concerns allows the Dashboard component to work with both Meshtastic and MeshCore without internal data shape forking.

Minimal Surface Area Changes: The PR achieves significant functionality expansion (~549 lines) while keeping the Dashboard component changes minimal (Dashboard.tsx:27, DashboardHeader.tsx:250). Existing Meshtastic behavior is preserved byte-for-byte through the default meshtasticDashboardSource.

Type Safety: Strong TypeScript usage throughout. The RawMeshCoreNode interface (dataSources.ts:96-105) properly encapsulates the server-side shape while the adapters handle normalization to NodeInfo.

Implementation Quality

MeshCore Data Transformation: The adapter correctly maps MeshCore data to the existing NodeInfo shape (dataSources.ts:142-161), including proper handling of:

  • Public key → user.id mapping
  • Optional position data (only when both lat/lng exist)
  • advType → role mapping with proper labels
  • Sensible nodeNum: 0 default (unused by MeshCore charts)

Channel Suffix Handling: Excellent regex-based label resolution for MeshCore's _ch<N> suffixes (TelemetryChart.tsx:150-171). The pattern mc_battery_volts_ch2"Battery (ch2)" is intuitive and handles edge cases gracefully.

Permission Integration: Proper permission checking in MeshCoreTelemetryView.tsx:31 using hasPermission('dashboard', 'write').

Test Coverage

Comprehensive Testing: New tests thoroughly cover the critical path:

  • dataSources.test.ts: 16 new tests covering adapter mapping, role labels, and defaults
  • TelemetryChart.test.ts: Channel suffix label resolution
  • Tests verify both wrapped ({ data: [...] }) and unwrapped array API responses

Regression Protection: Tests ensure Meshtastic behavior remains unchanged while validating new MeshCore functionality.

Performance & Maintainability

Query Key Scoping: Proper TanStack Query cache scoping by sourceId and dataSource.kind (useDashboardData.ts:116) prevents cross-source data leaks.

Solar Defaults: Smart per-source solar overlay defaults. MeshCore focuses on LPP sensor types (mc_battery_volts_ch1..ch4, env sensors) while excluding status counters.

Lazy Loading: MeshCore nodes only load when sourceId is provided (dataSources.ts:134), preventing unnecessary requests.

⚠️ Minor Observations

Type Coercion: The role mapping (dataSources.ts:168) handles both number and string types with Number(advType). Consider adding a comment explaining why this flexibility is needed.

Error Handling: MeshCore adapter tolerates both wrapped and unwrapped API responses (dataSources.ts:139-141). While robust, this suggests potential API inconsistency worth documenting.

Label Dictionary Size: The TELEMETRY_LABELS object is growing large (~143 lines in TelemetryChart.tsx). Consider extracting to a separate constants file for better maintainability.

🔒 Security & Best Practices

Input Validation: Proper encodeURIComponent(sourceId) usage in API calls.

Error Boundaries: Graceful fallbacks for unknown telemetry types and missing labels.

No Raw SQL: Adheres to project's raw SQL ban (ESLint enforced).

🚀 Design Decisions (Excellent)

Widget Hiding vs. Forking: Hiding custom widgets via showCustomWidgets: false instead of duplicating Dashboard component is the right architectural choice. Keeps diff manageable and allows future MeshCore widget expansion.

Global Favorites: Sharing telemetryFavorites across sources with (nodeId, telemetryType) keys makes sense for user workflow.

Default Preservation: Using Meshtastic adapter as default ensures zero regression risk for existing deployments.

📋 Final Assessment

This is a high-quality implementation that follows project patterns and TypeScript best practices. The abstraction layer is well-designed, test coverage is thorough, and the integration is clean.

Recommendation: ✅ APPROVE

The PR successfully closes the "favorited charts with nowhere to appear" gap for MeshCore while maintaining architectural integrity and backward compatibility.


Test Results: 5411/5411 tests passing (16 new tests added)
Manual Verification: ✅ TypeScript clean, ✅ Comprehensive test coverage

View CI run

@Yeraze Yeraze merged commit 706eddf into main May 22, 2026
19 checks passed
@Yeraze Yeraze deleted the feature/3139-meshcore-telemetry-dashboard branch May 22, 2026 17:05
Yeraze added a commit that referenced this pull request May 22, 2026
…ish (#3145)

Version bump and CHANGELOG entry for 4.6.5. Highlights:

- #3143 MQTT Bridge mirror dashboard (read-only Open button on bridge
  cards; suppresses every TX surface; MqttBridgeManager now exposes the
  Meshtastic-shaped methods the consolidated /api/poll + /api/connection
  endpoints expect).
- #3142 MeshCore Telemetry dashboard tab via source-agnostic Dashboard.
- #3141 MeshCore: preserve LPP channel byte in remote telemetry rows.
- #3140 DM: skip PKI flag when keyMismatchDetected.
- #3138 docs reorganization.
- #3137 unified: merge nodes across sources so labels show in the
  unified Nodes view.
- #3136 mqtt: allow standalone mqtt_bridge as client-proxy target.

CLAUDE.md version line updated to 4.6.5.

Co-authored-by: Claude Opus 4.7 (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