Skip to content

feat(channels): apply modem-preset display name to per-source channels view#3093

Merged
Yeraze merged 1 commit into
mainfrom
source-channels-preset-name
May 19, 2026
Merged

feat(channels): apply modem-preset display name to per-source channels view#3093
Yeraze merged 1 commit into
mainfrom
source-channels-preset-name

Conversation

@Yeraze

@Yeraze Yeraze commented May 19, 2026

Copy link
Copy Markdown
Owner

Summary

Follow-up to #3089. The unified channels picker now renders empty-name slot 0 with the firmware-derived label (MediumFast, LongFast, etc.) drawn from the device's modem preset, but the per-source Source Channels tab was still showing the synthetic "Primary" fallback. This PR applies the same logic to the per-source channel responses so the two views agree and match what MQTT gateways actually publish on the wire.

Changes

  • transformChannel (src/server/utils/channelView.ts) now emits a new displayName field alongside the raw name. Computed by a new computeChannelDisplayName helper:
    • trimmed name if set
    • else for slot 0: the modem preset's pascal-case label when a presetName option is passed, otherwise the legacy "Primary" fallback
    • else the raw (empty) name
      The raw name column is intentionally unchanged so channel-config edits still round-trip to firmware untouched.
  • /api/sources/:id/channels and /api/channels?sourceId=... now look up the source's persisted modem preset (lora.preset.<sourceId> setting, written by meshtasticManager on each LoRa config response) and pass the firmware-derived name into transformChannel. Cross-source listings without a sourceId query param fall back to "Primary" as before.
  • Channel type (src/types/device.ts) gains optional displayName, documented as the field the frontend should prefer.
  • ChannelsTab.getChannelName prefers displayName when present so the Source Channels tab matches the unified picker.

Test plan

  • npx tsc --noEmit -p tsconfig.json — clean
  • npx vitest run — full suite passes (4 new channelView cases for preset-derived display name, empty-slot-0 fallback, non-slot-0 handling, and the whitelist updated to include displayName)
  • Live in dev container: /api/sources/<sandbox-id>/channels returns { id: 0, name: "", displayName: "MediumFast", role: 1, roleName: "Primary" } (preset persisted from MEDIUM_FAST = 4)
  • /api/channels?sourceId=<sandbox-id> returns the same displayName
  • Frontend Source Channels tab renders MediumFast instead of "Primary" after hard refresh
  • System tests run on every PR via CI

🤖 Generated with Claude Code

…s view

The Source Channels tab in the UI was still rendering empty-name slot 0 as
"Primary" — a synthetic localization fallback — even though the unified
channels picker now shows the firmware-derived label (`MediumFast`,
`LongFast`, etc.) for the same channel. Apply the same logic to
per-source channel responses so the two views agree, and so the displayed
name matches what MQTT gateways actually publish on the wire.

- `transformChannel` (`src/server/utils/channelView.ts`) now emits a new
  `displayName` field alongside the raw `name` column. Computed via a new
  `computeChannelDisplayName` helper: trimmed `name` if set, otherwise the
  modem preset's pascal-case label for slot 0 (when a `presetName` option
  is passed), otherwise the legacy `"Primary"` fallback. The raw `name`
  column is unchanged so channel-config edits still round-trip to the
  firmware untouched. New `TransformChannelOptions.presetName` carries
  the hint.
- `/api/sources/:id/channels` (`src/server/routes/sourceRoutes.ts`) and
  `/api/channels?sourceId=` (`src/server/server.ts`) both look up the
  source's persisted modem preset (`lora.preset.<sourceId>` setting,
  written by `meshtasticManager` on each LoRa config response) and pass
  the firmware-derived name into `transformChannel`. Cross-source listings
  with no `sourceId` query param fall back to `"Primary"` as before.
- `Channel` type (`src/types/device.ts`) gains optional `displayName`,
  documented as the field the frontend should prefer for rendering.
- `ChannelsTab.getChannelName` prefers `displayName` when present so the
  Source Channels tab matches the unified picker.

Tests: 4 new channelView.test cases covering preset-derived display name,
empty-slot-0 fallback to "Primary", non-slot-0 empty handling, and the
existing whitelist updated to include `displayName`. Full suite green.

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

github-actions Bot commented May 19, 2026

Copy link
Copy Markdown

Claude finished @Yeraze's task —— View job


🔍 Code Review: Channel Preset Display Name Alignment

I've completed a thorough review of PR #3093. This is a well-implemented feature that aligns channel display names between the unified channels picker and per-source views.

Strengths

1. Architecture & Design

  • Clean separation of concerns: The logic is properly isolated in channelView.ts with a focused computeChannelDisplayName helper
  • Consistent API design: Both /api/sources/:id/channels and /api/channels?sourceId=... follow the same pattern for preset resolution
  • Backwards compatibility: Raw name column remains unchanged, ensuring channel config edits round-trip correctly

2. Security & Permissions

  • MM-SEC-2 compliance: Correctly uses transformChannel projection to avoid PSK leaks
  • Proper permission scoping: Channel access is correctly gated by per-channel and per-source permissions
  • Safe fallbacks: Graceful degradation when preset data isn't available

3. Code Quality

  • Excellent documentation: Clear comments explaining the firmware-derived naming and fallback logic
  • Type safety: Proper TypeScript usage with optional displayName field in the Channel interface
  • Error handling: Robust fallbacks for missing settings/presets with appropriate logging

🎯 Implementation Details

Core Logic (channelView.ts:74-82)

export function computeChannelDisplayName(
  channel: { id: number; name?: string | null },
  presetName?: string | null,
): string {
  const trimmed = (channel.name ?? '').trim();
  if (trimmed) return trimmed;
  if (channel.id === 0) return presetName ?? PRIMARY_CHANNEL_NAME;
  return trimmed;
}
  • ✅ Correctly prioritizes explicit channel names
  • ✅ Only applies preset fallback to slot 0 (Primary channel)
  • ✅ Maintains empty string for unused slots > 0

Frontend Integration (ChannelsTab.tsx:333-341)

if (channel.displayName && channel.displayName.trim()) {
  return channel.displayName;
}
if (!channel.name?.trim() && channelNum === 0) {
  return t('channels.primary');
}
return channel.name;
  • ✅ Prefers server-computed displayName
  • ✅ Falls back to localized "Primary" when server preset unknown
  • ✅ Maintains backward compatibility

📋 Test Coverage

Comprehensive test cases added:

  • ✅ Preset-derived display names for slot 0
  • ✅ Fallback to "Primary" when no preset available
  • ✅ Non-slot-0 channels remain unchanged
  • ✅ Whitelist updated to include displayName field

🔍 Potential Areas for Improvement

1. Minor: Error Handling Enhancement

// Current (sourceRoutes.ts:772)
} catch (err) {
  logger.debug(`Failed to load preset for source ${source.id}:`, err);
}

// Suggestion: Consider differentiating error types
} catch (err) {
  if (err instanceof DatabaseError) {
    logger.warn(`Database error loading preset for ${source.id}:`, err);
  } else {
    logger.debug(`Failed to load preset for ${source.id}:`, err);
  }
}

2. Minor: Cache Optimization Opportunity
The preset lookup happens on every channels request. For high-traffic deployments, consider caching the preset name in memory and invalidating when LoRa config changes.

3. Documentation Enhancement
Consider adding a code comment in ChannelsTab.tsx explaining why the frontend still has a fallback to t('channels.primary') even though the server now provides displayName.

🚀 Performance & Scalability

  • Efficient: Minimal overhead - only one additional settings lookup per request
  • Scalable: No N+1 queries or expensive operations
  • Database agnostic: Uses existing settings infrastructure

🔒 Security Assessment

  • No new attack vectors: Uses existing permission patterns
  • Information disclosure: Only exposes user-friendly preset names, not sensitive config
  • Input validation: Proper handling of malformed/missing preset values

Compliance with Repo Standards

  • Multi-source architecture: Properly scoped by sourceId
  • Database patterns: Uses settings service, not raw SQL
  • Error handling: Graceful degradation with appropriate logging
  • TypeScript: Full type safety maintained
  • Testing: Comprehensive test coverage for new functionality

🎉 Final Assessment

This is a high-quality implementation that:

  • Solves the stated problem elegantly
  • Follows all repository patterns and conventions
  • Maintains backward compatibility
  • Includes proper testing
  • Has excellent documentation

The code is production-ready and demonstrates strong understanding of the codebase architecture.

Recommendation: ✅ Approve


@Yeraze Yeraze merged commit 98b6567 into main May 19, 2026
16 checks passed
@Yeraze Yeraze deleted the source-channels-preset-name branch May 19, 2026 21:59
Yeraze added a commit that referenced this pull request May 19, 2026
Patch release covering the MQTT-ingest rework (#3089) and follow-on UX
fixes (#3093 channel display-name, #3091 Channel Database move to Global)
plus the usual dependency bumps and minor fixes.

Version bumps across all five canonical files: package.json,
package-lock.json (regenerated), helm/meshmonitor/Chart.yaml,
desktop/package.json, desktop/src-tauri/tauri.conf.json.

CHANGELOG: organized under [4.6.2] with the user-visible MQTT/channel
items called out as the headline changes.

CLAUDE.md:
- Migration count 62+ → 63+ (latest: 063_drop_source_id_from_channel_database)
- "Every row carries a sourceId" rule gains an exception note for
  channel_database — it's intentionally global (decryption service tries
  every enabled row regardless of source) and migration 063 dropped the
  dead sourceId column.

Blog post + news.json: "MeshMonitor v4.6.2 — MQTT in the Unified views & cleaner channel names" — three things users will notice immediately
(slot-0 channel renamed from "Primary" to the modem-preset label,
Channel Database moved to Global Settings, MQTT sources now participate
in Unified Messages and Unified Telemetry) plus action items after
upgrade.

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