Skip to content

Commit c273230

Browse files
Yerazeclaude
andauthored
feat(meshcore): opt-in auto-retry for automated channel sends on zero-heard miss (#3979) (#3988)
Part 2 of #3979 (builds on merged Part 1, PR #3987, which made the "heard repeaters" echo-attribution trustworthy). A MeshCore channel/broadcast send is an unacked, fire-and-forget flood, so there is no firmware ACK to confirm delivery. When the Part 1 echo signal reports that ZERO repeaters re-flooded our packet within 30s, the message likely reached no one. This adds an opt-in, one-shot resend for that case. - New global setting `meshcoreChannelRetryEnabled` (default OFF), wired through VALID_SETTINGS_KEYS, SettingsContext, and a new "MeshCore Messaging" section in global Settings (SettingsTab). - Applies to AUTOMATED channel senders only (Automation Engine action.sendMessage, Auto-Acknowledge, auto-responder, auto-announce, timer triggers), threaded via a new `autoRetryOnMiss` opt-in flag on sendMessage/sendMessageWithResult/performScopedSend. User-initiated route sends never opt in, so they never retry. - New channel-retry state machine (`pendingChannelRetries`, `maybeArmChannelRetry`, `handleChannelRetryTimeout`) parallel to and non-colliding with the DM ack-retry (#3977/#3980): the DM path keys on the firmware ACK CRC (toPublicKey set); the channel path keys on the echo-heard signal (channelIdx set) — mutually exclusive branches of performScopedSend. - One-shot: the resend goes out with isAutoRetry=true (no new message row, no `message`/bus emit — so it cannot spawn a fresh automation) and autoRetryOnMiss=false (re-registered for echo correlation but never re-armed). - Pending channel retries cleared in the disconnect() bulk cleanup. - Tests: src/server/meshcoreManager.channelRetry.test.ts covers zero-heard resend, heard→no-resend, no-bus-reentry, one-shot, user-send never arms, setting-off never arms, disconnect clears, and DM/channel non-collision. - Docs: docs/features/meshcore.md (new section) + automation-engine.md note. Closes #3979 Claude-Session: https://claude.ai/code/session_015AFBA76hsjqhsXe1BdnYub Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
1 parent e75374f commit c273230

9 files changed

Lines changed: 466 additions & 18 deletions

File tree

docs/features/automation-engine.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,14 @@ Sends text to a channel or as a DM, with full `{{ }}` token interpolation in the
189189
The overall send is a **source × channel matrix**: each selected source posts to the matching local
190190
slot of each selected channel.
191191

192+
> **MeshCore channel-send auto-retry** — because a MeshCore channel/broadcast send is an unacked
193+
> flood, the `Send message` action (like every automated MeshCore channel sender) can optionally be
194+
> resent once if no repeater is heard re-flooding it within 30 seconds. This is a global, opt-in,
195+
> one-shot behavior (off by default) configured in **Settings → MeshCore Messaging**; it never
196+
> retries user-typed messages, never retries direct messages (those have their own always-on ACK
197+
> retry), and the resend can never trigger a fresh automation. See
198+
> [Automated Channel-Send Auto-Retry](/features/meshcore#automated-channel-send-auto-retry).
199+
192200
### Manage the node
193201

194202
Runs an admin/management operation on the subject node: **Favorite / Unfavorite**, **Ignore /

docs/features/meshcore.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -429,6 +429,20 @@ Timer Triggers schedule recurring actions independent of incoming traffic:
429429
- **Three actions** — send a **text** message (token expansion supported) to a channel or contact, fire a MeshCore **advert**, or **run a script** (token-expanded args).
430430
- **Last-run telemetry** — the UI surfaces the last fire time and outcome per trigger.
431431

432+
## Automated Channel-Send Auto-Retry
433+
434+
MeshCore channel (broadcast) messages are unacked, fire-and-forget floods — unlike a direct message, there is no firmware ACK to confirm delivery. MeshMonitor instead listens for nearby repeaters re-flooding your own packet (the "heard repeaters" signal, see [Message route line](#message-route-line)). When **no** repeater is heard, the message likely reached no one.
435+
436+
The **MeshCore Messaging** section of global **Settings** hosts an opt-in toggle, **Auto-retry automated MeshCore channel sends** (default **off**). When enabled:
437+
438+
- An **automated** channel send that hears **zero** repeaters within **30 seconds** is resent **exactly once**.
439+
- It applies only to automated senders: the [Automation Engine](/features/automation-engine) `Send message` action, Auto-Acknowledge, the [Auto-Responder](#auto-responder), [Auto-Announce](#auto-announce), and [Timer Triggers](#timer-triggers).
440+
- **User-initiated sends are never retried** — a message you type into the send bar goes out once regardless of this setting.
441+
- It is **one-shot**: the resend is never itself retried, so at most one extra transmission ever occurs per logical send.
442+
- The resend does **not** create a second message bubble and does **not** re-enter the automation event bus, so it can never trigger a fresh automation.
443+
444+
This is **distinct from the direct-message retry**, which is always on and follows the firmware's own same-path/flood ACK cadence. Because a channel send has no delivery ACK, the retry can only guess from the heard-repeater signal — so with this enabled you may occasionally see a duplicate on the mesh if a late echo arrives right around the 30-second mark. Leave it off if duplicates are unacceptable for your deployment.
445+
432446
## Room Servers
433447

434448
Room servers (advType=3) are BBS-style MeshCore nodes that store posts and push-sync them to connected clients. The **Rooms** view in the MeshCore page lists discovered room servers and provides:

src/components/SettingsTab.tsx

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ interface SettingsTabProps {
9494
}
9595

9696
const GLOBAL_SECTIONS = new Set([
97-
'settings-language', 'settings-units', 'settings-appearance', 'settings-link-previews', 'settings-map',
97+
'settings-language', 'settings-units', 'settings-appearance', 'settings-link-previews', 'settings-meshcore-messaging', 'settings-map',
9898
'settings-security',
9999
'settings-apprise-server', 'settings-backup', 'settings-channel-database',
100100
'settings-maintenance', 'settings-auto-upgrade', 'settings-analytics',
@@ -179,6 +179,8 @@ const SettingsTab: React.FC<SettingsTabProps> = ({
179179
setEnableAudioNotifications,
180180
linkPreviewsEnabled,
181181
setLinkPreviewsEnabled,
182+
meshcoreChannelRetryEnabled,
183+
setMeshcoreChannelRetryEnabled,
182184
nodeDimmingEnabled,
183185
setNodeDimmingEnabled,
184186
nodeDimmingStartHours,
@@ -240,6 +242,7 @@ const SettingsTab: React.FC<SettingsTabProps> = ({
240242
const [localPacketLogMaxCount, setLocalPacketLogMaxCount] = useState(1000);
241243
const [localPacketLogMaxAgeHours, setLocalPacketLogMaxAgeHours] = useState(24);
242244
const [localLinkPreviewsEnabled, setLocalLinkPreviewsEnabled] = useState(linkPreviewsEnabled);
245+
const [localMeshcoreChannelRetryEnabled, setLocalMeshcoreChannelRetryEnabled] = useState(meshcoreChannelRetryEnabled);
243246
const [localSolarMonitoringEnabled, setLocalSolarMonitoringEnabled] = useState(solarMonitoringEnabled);
244247
const [localSolarMonitoringLatitude, setLocalSolarMonitoringLatitude] = useState(solarMonitoringLatitude);
245248
const [localSolarMonitoringLongitude, setLocalSolarMonitoringLongitude] = useState(solarMonitoringLongitude);
@@ -337,6 +340,10 @@ const SettingsTab: React.FC<SettingsTabProps> = ({
337340
const linkPreviewsOn = !(settings.linkPreviewsEnabled === '0' || settings.linkPreviewsEnabled === 'false');
338341
setLocalLinkPreviewsEnabled(linkPreviewsOn);
339342

343+
// Load MeshCore channel-send auto-retry (#3979). Absent key => disabled.
344+
const meshcoreChannelRetryOn = settings.meshcoreChannelRetryEnabled === '1' || settings.meshcoreChannelRetryEnabled === 'true';
345+
setLocalMeshcoreChannelRetryEnabled(meshcoreChannelRetryOn);
346+
340347
// Load LocalStats interval setting
341348
const statsInterval = parseInt(settings.localStatsIntervalMinutes || '15', 10);
342349
setLocalLocalStatsIntervalMinutes(statsInterval);
@@ -407,13 +414,14 @@ const SettingsTab: React.FC<SettingsTabProps> = ({
407414
setLocalNodeHopsCalculation(nodeHopsCalculation);
408415
setLocalDashboardSortOption(preferredDashboardSortOption);
409416
setLocalLinkPreviewsEnabled(linkPreviewsEnabled);
417+
setLocalMeshcoreChannelRetryEnabled(meshcoreChannelRetryEnabled);
410418
setLocalSolarMonitoringEnabled(solarMonitoringEnabled);
411419
setLocalSolarMonitoringLatitude(solarMonitoringLatitude);
412420
setLocalSolarMonitoringLongitude(solarMonitoringLongitude);
413421
setLocalSolarMonitoringAzimuth(solarMonitoringAzimuth);
414422
setLocalSolarMonitoringDeclination(solarMonitoringDeclination);
415423
setLocalHideIncompleteNodes(!showIncompleteNodes);
416-
}, [maxNodeAgeHours, inactiveNodeThresholdHours, inactiveNodeCheckIntervalMinutes, inactiveNodeCooldownHours, temperatureUnit, distanceUnit, positionHistoryLineStyle, telemetryVisualizationHours, favoriteTelemetryStorageDays, preferredSortField, preferredSortDirection, timeFormat, dateFormat, mapTileset, mapPinStyle, nodeHopsCalculation, preferredDashboardSortOption, linkPreviewsEnabled, solarMonitoringEnabled, solarMonitoringLatitude, solarMonitoringLongitude, solarMonitoringAzimuth, solarMonitoringDeclination, showIncompleteNodes, defaultMapCenterLat, defaultMapCenterLon, defaultMapCenterZoom, defaultLandingPage, appearanceMode, darkTheme, lightTheme]);
424+
}, [maxNodeAgeHours, inactiveNodeThresholdHours, inactiveNodeCheckIntervalMinutes, inactiveNodeCooldownHours, temperatureUnit, distanceUnit, positionHistoryLineStyle, telemetryVisualizationHours, favoriteTelemetryStorageDays, preferredSortField, preferredSortDirection, timeFormat, dateFormat, mapTileset, mapPinStyle, nodeHopsCalculation, preferredDashboardSortOption, linkPreviewsEnabled, meshcoreChannelRetryEnabled, solarMonitoringEnabled, solarMonitoringLatitude, solarMonitoringLongitude, solarMonitoringAzimuth, solarMonitoringDeclination, showIncompleteNodes, defaultMapCenterLat, defaultMapCenterLon, defaultMapCenterZoom, defaultLandingPage, appearanceMode, darkTheme, lightTheme]);
417425

418426
// Default solar monitoring lat/long to device position if still at 0
419427
useEffect(() => {
@@ -475,6 +483,7 @@ const SettingsTab: React.FC<SettingsTabProps> = ({
475483
localSolarMonitoringAzimuth !== solarMonitoringAzimuth ||
476484
localSolarMonitoringDeclination !== solarMonitoringDeclination ||
477485
localLinkPreviewsEnabled !== linkPreviewsEnabled ||
486+
localMeshcoreChannelRetryEnabled !== meshcoreChannelRetryEnabled ||
478487
localHideIncompleteNodes !== !showIncompleteNodes ||
479488
localHomoglyphEnabled !== initialHomoglyphEnabled ||
480489
localLocalStatsIntervalMinutes !== initialLocalStatsIntervalMinutes ||
@@ -491,6 +500,7 @@ const SettingsTab: React.FC<SettingsTabProps> = ({
491500
localSolarMonitoringEnabled, localSolarMonitoringLatitude, localSolarMonitoringLongitude, localSolarMonitoringAzimuth, localSolarMonitoringDeclination,
492501
solarMonitoringEnabled, solarMonitoringLatitude, solarMonitoringLongitude, solarMonitoringAzimuth, solarMonitoringDeclination,
493502
localLinkPreviewsEnabled, linkPreviewsEnabled,
503+
localMeshcoreChannelRetryEnabled, meshcoreChannelRetryEnabled,
494504
localHideIncompleteNodes, showIncompleteNodes, localHomoglyphEnabled, initialHomoglyphEnabled,
495505
localLocalStatsIntervalMinutes, initialLocalStatsIntervalMinutes,
496506
nodeDimmingEnabled, nodeDimmingStartHours, nodeDimmingMinOpacity, initialNodeDimmingSettings,
@@ -533,6 +543,7 @@ const SettingsTab: React.FC<SettingsTabProps> = ({
533543
setLocalSolarMonitoringAzimuth(solarMonitoringAzimuth);
534544
setLocalSolarMonitoringDeclination(solarMonitoringDeclination);
535545
setLocalLinkPreviewsEnabled(linkPreviewsEnabled);
546+
setLocalMeshcoreChannelRetryEnabled(meshcoreChannelRetryEnabled);
536547
setLocalHideIncompleteNodes(!showIncompleteNodes);
537548
setLocalHomoglyphEnabled(initialHomoglyphEnabled);
538549
setLocalLocalStatsIntervalMinutes(initialLocalStatsIntervalMinutes);
@@ -548,7 +559,7 @@ const SettingsTab: React.FC<SettingsTabProps> = ({
548559
dateFormat, mapTileset, mapPinStyle, iconStyle, neighborInfoMinZoom, defaultMapCenterLat, defaultMapCenterLon, defaultMapCenterZoom, defaultLandingPage, appearanceMode, darkTheme, lightTheme, nodeHopsCalculation, preferredDashboardSortOption,
549560
initialPacketMonitorSettings, solarMonitoringEnabled, solarMonitoringLatitude,
550561
solarMonitoringLongitude, solarMonitoringAzimuth, solarMonitoringDeclination, showIncompleteNodes,
551-
linkPreviewsEnabled,
562+
linkPreviewsEnabled, meshcoreChannelRetryEnabled,
552563
initialHomoglyphEnabled, initialLocalStatsIntervalMinutes, initialNodeDimmingSettings,
553564
setNodeDimmingEnabled, setNodeDimmingStartHours, setNodeDimmingMinOpacity,
554565
initialAnalyticsProvider, initialAnalyticsConfig, initialAppriseApiServerUrl]);
@@ -601,6 +612,7 @@ const SettingsTab: React.FC<SettingsTabProps> = ({
601612
solarMonitoringAzimuth: localSolarMonitoringAzimuth.toString(),
602613
solarMonitoringDeclination: localSolarMonitoringDeclination.toString(),
603614
linkPreviewsEnabled: localLinkPreviewsEnabled ? '1' : '0',
615+
meshcoreChannelRetryEnabled: localMeshcoreChannelRetryEnabled ? '1' : '0',
604616
hideIncompleteNodes: localHideIncompleteNodes ? '1' : '0',
605617
homoglyphEnabled: String(localHomoglyphEnabled),
606618
localStatsIntervalMinutes: localLocalStatsIntervalMinutes.toString(),
@@ -653,6 +665,7 @@ const SettingsTab: React.FC<SettingsTabProps> = ({
653665
onSolarMonitoringAzimuthChange(localSolarMonitoringAzimuth);
654666
onSolarMonitoringDeclinationChange(localSolarMonitoringDeclination);
655667
setLinkPreviewsEnabled(localLinkPreviewsEnabled);
668+
setMeshcoreChannelRetryEnabled(localMeshcoreChannelRetryEnabled);
656669
setShowIncompleteNodes(!localHideIncompleteNodes);
657670

658671
// Update initial packet monitor settings after successful save
@@ -683,7 +696,7 @@ const SettingsTab: React.FC<SettingsTabProps> = ({
683696
localTimeFormat, localDateFormat, localMapTileset, localMapPinStyle, localIconStyle, localNeighborInfoMinZoom, localDefaultMapCenterLat, localDefaultMapCenterLon, localDefaultMapCenterZoom, localDefaultLandingPage, localAppearanceMode, localDarkTheme, localLightTheme, getLocalEffectiveTheme,
684697
localNodeHopsCalculation, localDashboardSortOption, localPacketLogEnabled, localPacketLogMaxCount, localPacketLogMaxAgeHours,
685698
localSolarMonitoringEnabled, localSolarMonitoringLatitude, localSolarMonitoringLongitude,
686-
localSolarMonitoringAzimuth, localSolarMonitoringDeclination, localLinkPreviewsEnabled, setLinkPreviewsEnabled, localHideIncompleteNodes, localHomoglyphEnabled, localLocalStatsIntervalMinutes,
699+
localSolarMonitoringAzimuth, localSolarMonitoringDeclination, localLinkPreviewsEnabled, setLinkPreviewsEnabled, localMeshcoreChannelRetryEnabled, setMeshcoreChannelRetryEnabled, localHideIncompleteNodes, localHomoglyphEnabled, localLocalStatsIntervalMinutes,
687700
onMaxNodeAgeChange, onInactiveNodeThresholdHoursChange, onInactiveNodeCheckIntervalMinutesChange,
688701
onInactiveNodeCooldownHoursChange, onTemperatureUnitChange, onDistanceUnitChange, onPositionHistoryLineStyleChange,
689702
onTelemetryVisualizationChange, onFavoriteTelemetryStorageDaysChange, onPreferredSortFieldChange,
@@ -824,6 +837,7 @@ const SettingsTab: React.FC<SettingsTabProps> = ({
824837
setLocalSolarMonitoringAzimuth(0);
825838
setLocalSolarMonitoringDeclination(30);
826839
setLocalLinkPreviewsEnabled(true);
840+
setLocalMeshcoreChannelRetryEnabled(false);
827841

828842
// Update parent component with defaults
829843
onMaxNodeAgeChange(24);
@@ -849,6 +863,7 @@ const SettingsTab: React.FC<SettingsTabProps> = ({
849863
onSolarMonitoringAzimuthChange(0);
850864
onSolarMonitoringDeclinationChange(30);
851865
setLinkPreviewsEnabled(true);
866+
setMeshcoreChannelRetryEnabled(false);
852867

853868
// Update initial packet monitor settings
854869
setInitialPacketMonitorSettings({ enabled: false, maxCount: 1000, maxAgeHours: 24 });
@@ -1076,6 +1091,7 @@ const SettingsTab: React.FC<SettingsTabProps> = ({
10761091
{ id: 'settings-sorting', label: t('settings.sorting') },
10771092
{ id: 'settings-appearance', label: t('settings.appearance') },
10781093
{ id: 'settings-link-previews', label: t('settings.link_previews', 'Link Previews') },
1094+
{ id: 'settings-meshcore-messaging', label: t('settings.meshcore_messaging', 'MeshCore Messaging') },
10791095
{ id: 'settings-map', label: t('settings.map') },
10801096
{ id: 'settings-node-display', label: t('settings.node_display') },
10811097
{ id: 'settings-telemetry', label: t('settings.telemetry') },
@@ -1378,6 +1394,23 @@ const SettingsTab: React.FC<SettingsTabProps> = ({
13781394
</p>
13791395
</div>}
13801396

1397+
{show('settings-meshcore-messaging') && <div id="settings-meshcore-messaging" className="settings-section">
1398+
<h3 style={{ display: 'flex', alignItems: 'center', gap: '0.75rem' }}>
1399+
<label style={{ display: 'flex', alignItems: 'center', gap: '0.5rem', margin: 0, cursor: 'pointer' }}>
1400+
<input
1401+
type="checkbox"
1402+
checked={localMeshcoreChannelRetryEnabled}
1403+
onChange={(e) => setLocalMeshcoreChannelRetryEnabled(e.target.checked)}
1404+
style={{ cursor: 'pointer' }}
1405+
/>
1406+
<span>{t('settings.meshcore_channel_retry_enabled', 'Auto-retry automated MeshCore channel sends')}</span>
1407+
</label>
1408+
</h3>
1409+
<p className="setting-description">
1410+
{t('settings.meshcore_channel_retry_description', 'When enabled, an AUTOMATED MeshCore channel/broadcast message (Automation Engine, Auto-Acknowledge, auto-responder, auto-announce and timer triggers) that hears no repeaters within 30 seconds is resent exactly once. User-initiated sends are never retried. This is opt-in, channel-only, and one-shot — distinct from the always-on retry for direct messages. You may occasionally see a duplicate on the mesh.')}
1411+
</p>
1412+
</div>}
1413+
13811414
{show('settings-map') && <div id="settings-map" className="settings-section">
13821415
<h3>{t('settings.map')}</h3>
13831416
<div className="setting-item">

src/contexts/SettingsContext.tsx

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,12 @@ interface SettingsContextType {
106106
enableAudioNotifications: boolean;
107107
/** Global toggle: fetch & render OpenGraph link preview cards for URLs in messages. */
108108
linkPreviewsEnabled: boolean;
109+
/**
110+
* Global opt-in (issue #3979, default false): auto-retry an AUTOMATED MeshCore
111+
* channel send once, 30s later, when zero repeaters were heard. Automated
112+
* senders only; never user-initiated sends. Distinct from the DM ack-retry.
113+
*/
114+
meshcoreChannelRetryEnabled: boolean;
109115
nodeDimmingEnabled: boolean;
110116
nodeDimmingStartHours: number;
111117
nodeDimmingMinOpacity: number;
@@ -154,6 +160,7 @@ interface SettingsContextType {
154160
setSolarMonitoringDeclination: (declination: number) => void;
155161
setEnableAudioNotifications: (enabled: boolean) => void;
156162
setLinkPreviewsEnabled: (enabled: boolean) => void;
163+
setMeshcoreChannelRetryEnabled: (enabled: boolean) => void;
157164
mutedChannels: MutedChannel[];
158165
mutedDMs: MutedDM[];
159166
muteChannel: (channelId: number, muteUntil: number | null) => Promise<void>;
@@ -418,6 +425,11 @@ export const SettingsProvider: React.FC<SettingsProviderProps> = ({ children, ba
418425
// the previous always-on behavior; loaded from the server in loadServerSettings.
419426
const [linkPreviewsEnabled, setLinkPreviewsEnabledState] = useState<boolean>(true);
420427

428+
// MeshCore automated-channel-send auto-retry (issue #3979) - server-backed
429+
// (global). Defaults to false (opt-in); loaded from the server in
430+
// loadServerSettings.
431+
const [meshcoreChannelRetryEnabled, setMeshcoreChannelRetryEnabledState] = useState<boolean>(false);
432+
421433
// Node dimming settings - localStorage only
422434
const [nodeDimmingEnabled, setNodeDimmingEnabledState] = useState<boolean>(() => {
423435
const saved = localStorage.getItem('nodeDimmingEnabled');
@@ -822,6 +834,11 @@ export const SettingsProvider: React.FC<SettingsProviderProps> = ({ children, ba
822834
setLinkPreviewsEnabledState(enabled);
823835
};
824836

837+
// MeshCore channel-retry setter updates state only - persisted server-side
838+
const setMeshcoreChannelRetryEnabled = (enabled: boolean) => {
839+
setMeshcoreChannelRetryEnabledState(enabled);
840+
};
841+
825842
// Solar monitoring setters update state only - values are persisted server-side
826843
const setSolarMonitoringEnabled = (enabled: boolean) => {
827844
setSolarMonitoringEnabledState(enabled);
@@ -1328,6 +1345,13 @@ export const SettingsProvider: React.FC<SettingsProviderProps> = ({ children, ba
13281345
setLinkPreviewsEnabledState(enabled);
13291346
}
13301347

1348+
// MeshCore channel-send auto-retry (#3979) - database-only. Absent key
1349+
// means default (disabled); only an explicit '1'/'true' turns it on.
1350+
if (settings.meshcoreChannelRetryEnabled !== undefined) {
1351+
const enabled = settings.meshcoreChannelRetryEnabled === '1' || settings.meshcoreChannelRetryEnabled === 'true';
1352+
setMeshcoreChannelRetryEnabledState(enabled);
1353+
}
1354+
13311355
// Solar monitoring settings - database-only, no localStorage persistence
13321356
if (settings.solarMonitoringEnabled !== undefined) {
13331357
const enabled = settings.solarMonitoringEnabled === '1' || settings.solarMonitoringEnabled === 'true';
@@ -1533,6 +1557,7 @@ export const SettingsProvider: React.FC<SettingsProviderProps> = ({ children, ba
15331557
customTilesets,
15341558
isLoadingThemes,
15351559
linkPreviewsEnabled,
1560+
meshcoreChannelRetryEnabled,
15361561
solarMonitoringEnabled,
15371562
solarMonitoringLatitude,
15381563
solarMonitoringLongitude,
@@ -1581,6 +1606,7 @@ export const SettingsProvider: React.FC<SettingsProviderProps> = ({ children, ba
15811606
updateCustomTileset,
15821607
deleteCustomTileset,
15831608
setLinkPreviewsEnabled,
1609+
setMeshcoreChannelRetryEnabled,
15841610
setSolarMonitoringEnabled,
15851611
setSolarMonitoringLatitude,
15861612
setSolarMonitoringLongitude,

0 commit comments

Comments
 (0)