Skip to content

.pr_agent_accepted_suggestions

qodo-merge-bot edited this page Nov 29, 2025 · 22 revisions
                     PR 2436 (2025-11-26)                    
[learned best practice] Guard modal open call

✅ Guard modal open call

Ensure the modal instance is valid and not destroyed before calling open to prevent runtime errors if initialization failed or it was disposed.

tabs/ports.js [49-53]

 function showMSPWarning() {
-    if (mspWarningModal) {
+    if (mspWarningModal && typeof mspWarningModal.open === 'function') {
         mspWarningModal.open();
     }
 }

Suggestion importance[1-10]: 6

__

Why: Relevant best practice - Gate UI behavior with guards and validate DOM elements before use to avoid runtime errors.



                     PR 2434 (2025-11-26)                    
[possible issue] Fix silent error handling bug

✅ Fix silent error handling bug

Refactor the appendFile IPC handler to be a simple async function that properly rejects on error, instead of resolving with an error object, to enable correct error handling in the renderer process.

js/main/main.js [320-327]

-return new Promise(async resolve => {
-  try {
-    await appendFile(filename, data);
-    resolve(false)
-  } catch (err) {
-    resolve(err);
-  }
-});
+try {
+  await appendFile(filename, data);
+  return false;
+} catch (err) {
+  // Re-throwing the error will cause the promise on the renderer side to be rejected.
+  throw err;
+}

Suggestion importance[1-10]: 8

__

Why: The suggestion correctly identifies a promise anti-pattern in the new appendFile handler that would lead to silent failures and provides the correct fix.



                     PR 2433 (2025-11-25)                    
[possible issue] Guard and sanitize reboot polling loop

✅ Guard and sanitize reboot polling loop

Refactor pollForRebootCompletion to correctly use the promise-based ConnectionSerial.getDevices API. Additionally, add a guard flag to prevent race conditions and ensure the polling interval is always cleared.

js/protocols/stm32.js [61-99]

 STM32_protocol.prototype.pollForRebootCompletion = function(port, hex, options, onSuccess, onTimeout) {
     var self = this;
     var intervalMs = 200;
     var retries = 0;
     var maxRetries = 50; // timeout after intervalMs * 50 (10 seconds)
+    var finished = false;
+
+    var finishOnce = function(fn) {
+        if (!finished) {
+            finished = true;
+            clearInterval(pollInterval);
+            fn && fn();
+        }
+    };
 
     var pollInterval = setInterval(function() {
+        if (finished) return;
         retries++;
         if (retries > maxRetries) {
-            clearInterval(pollInterval);
-            onTimeout();
+            finishOnce(onTimeout);
             return;
         }
 
         // Check for DFU devices first
         PortHandler.check_usb_devices(function(dfu_available) {
+            if (finished) return;
             if (dfu_available) {
-                clearInterval(pollInterval);
-                STM32DFU.connect(usbDevices, hex, options);
+                finishOnce(function() {
+                    STM32DFU.connect(usbDevices, hex, options);
+                });
                 return;
             }
 
-            // Check for serial port
-            ConnectionSerial.getDevices(function(devices) {
-                if (devices && devices.includes(port)) {
-                    // Serial port reappeared - try to connect
-                    CONFIGURATOR.connection.connect(port, {bitrate: self.baud, parityBit: 'even', stopBits: 'one'}, function (openInfo) {
-                        if (openInfo) {
-                            clearInterval(pollInterval);
-                            onSuccess();
-                        } else {
-                            GUI.connect_lock = false;
+            // Check for serial port (promise-based)
+            ConnectionSerial.getDevices().then(function(devices) {
+                if (finished) return;
+                if (Array.isArray(devices) && devices.includes(port)) {
+                    CONFIGURATOR.connection.connect(
+                        port,
+                        { bitrate: self.baud, parityBit: 'even', stopBits: 'one' },
+                        function(openInfo) {
+                            if (finished) return;
+                            if (openInfo) {
+                                finishOnce(onSuccess);
+                            } else {
+                                GUI.connect_lock = false;
+                            }
                         }
-                    });
+                    );
                 }
+            }).catch(function() {
+                // ignore errors and keep polling
             });
         });
     }, intervalMs);
 };

Suggestion importance[1-10]: 9

__

Why: The suggestion correctly identifies a critical bug where the pollForRebootCompletion function uses ConnectionSerial.getDevices with a callback, but the function was changed in the PR to be async and no longer accepts one, which would break the serial reconnection logic.


[incremental [*]] Scope and harden response listener

✅ Scope and harden response listener

Improve the waitForResponse function by ensuring the receive listener only processes data for the current connection and by using a more robust method for string conversion.

js/protocols/stm32.js [101-139]

-// Waits for a specific response from the serial connection with timeout
 STM32_protocol.prototype.waitForResponse = function(expectedString, timeoutMs, callback) {
     var receivedData = '';
     var timeoutHandle = null;
     var onReceiveListener = null;
+    var connectionId = CONFIGURATOR.connection && CONFIGURATOR.connection._connectionId;
 
     var cleanup = function() {
         if (timeoutHandle) {
             clearTimeout(timeoutHandle);
             timeoutHandle = null;
         }
-        if (onReceiveListener) {
+        if (onReceiveListener && CONFIGURATOR.connection) {
             CONFIGURATOR.connection.removeOnReceiveCallback(onReceiveListener);
             onReceiveListener = null;
         }
     };
 
-    // Set up timeout
+    // Bail out early if no active connection
+    if (!connectionId) {
+        callback(false, receivedData);
+        return;
+    }
+
     timeoutHandle = setTimeout(function() {
         cleanup();
         console.log('Timeout waiting for response:', expectedString);
         callback(false, receivedData);
     }, timeoutMs);
 
-    // Set up receive listener
     onReceiveListener = function(info) {
+        // Ignore data from other connections
+        if (info.connectionId && info.connectionId !== connectionId) {
+            return;
+        }
         var data = new Uint8Array(info.data);
-        var str = String.fromCharCode.apply(null, data);
+        // Robust conversion without apply to avoid call-stack issues on large buffers
+        var decoder = new TextDecoder('utf-8');
+        var str = decoder.decode(data);
         receivedData += str;
 
-        // Check if we received the expected string
-        if (receivedData.includes(expectedString)) {
+        if (receivedData.indexOf(expectedString) !== -1) {
             cleanup();
             callback(true, receivedData);
         }
     };
 
     CONFIGURATOR.connection.addOnReceiveCallback(onReceiveListener);
 };

Suggestion importance[1-10]: 7

__

Why: The suggestion correctly identifies a potential race condition where data from a different connection could be processed, and proposes a robust fix by checking connectionId, which improves the reliability of the response handling logic.


[learned best practice] Guard and cleanup listener once

✅ Guard and cleanup listener once

Add a one-time guard to ensure the callback fires once and the listener is always removed even on rapid multiple matches.

js/protocols/stm32.js [102-139]

 STM32_protocol.prototype.waitForResponse = function(expectedString, timeoutMs, callback) {
     var receivedData = '';
     var timeoutHandle = null;
     var onReceiveListener = null;
+    var done = false;
 
-    var cleanup = function() {
+    var finalize = function(success) {
+        if (done) return;
+        done = true;
         if (timeoutHandle) {
             clearTimeout(timeoutHandle);
             timeoutHandle = null;
         }
         if (onReceiveListener) {
             CONFIGURATOR.connection.removeOnReceiveCallback(onReceiveListener);
             onReceiveListener = null;
         }
+        callback(success, receivedData);
     };
 
-    // Set up timeout
     timeoutHandle = setTimeout(function() {
-        cleanup();
         console.log('Timeout waiting for response:', expectedString);
-        callback(false, receivedData);
+        finalize(false);
     }, timeoutMs);
 
-    // Set up receive listener
     onReceiveListener = function(info) {
         var data = new Uint8Array(info.data);
         var str = String.fromCharCode.apply(null, data);
         receivedData += str;
-
-        // Check if we received the expected string
         if (receivedData.includes(expectedString)) {
-            cleanup();
-            callback(true, receivedData);
+            finalize(true);
         }
     };
 
     CONFIGURATOR.connection.addOnReceiveCallback(onReceiveListener);
 };

Suggestion importance[1-10]: 6

__

Why: Relevant best practice - Avoid leaking listeners; always remove event handlers after use to prevent broken bindings and memory leaks.


[possible issue] Prevent multiple callback invocations

✅ Prevent multiple callback invocations

To prevent a race condition in waitForResponse, add a flag to ensure the callback is only executed once, even if multiple data chunks are received in rapid succession.

js/protocols/stm32.js [114-151]

 STM32_protocol.prototype.waitForResponse = function(expectedString, timeoutMs, callback) {
     var receivedData = '';
     var timeoutHandle = null;
     var onReceiveListener = null;
+    var callbackFired = false;
 
     var cleanup = function() {
         if (timeoutHandle) {
             clearTimeout(timeoutHandle);
             timeoutHandle = null;
         }
         if (onReceiveListener) {
             CONFIGURATOR.connection.removeOnReceiveCallback(onReceiveListener);
             onReceiveListener = null;
         }
     };
 
+    var executeCallback = function(success, data) {
+        if (!callbackFired) {
+            callbackFired = true;
+            cleanup();
+            callback(success, data);
+        }
+    };
+
     // Set up timeout
     timeoutHandle = setTimeout(function() {
-        cleanup();
         console.log('Timeout waiting for response:', expectedString);
-        callback(false, receivedData);
+        executeCallback(false, receivedData);
     }, timeoutMs);
 
     // Set up receive listener
     onReceiveListener = function(info) {
         var data = new Uint8Array(info.data);
         var str = String.fromCharCode.apply(null, data);
         receivedData += str;
 
         // Check if we received the expected string
         if (receivedData.includes(expectedString)) {
-            cleanup();
-            callback(true, receivedData);
+            executeCallback(true, receivedData);
         }
     };
 
     CONFIGURATOR.connection.addOnReceiveCallback(onReceiveListener);
 };

Suggestion importance[1-10]: 6

__

Why: The suggestion correctly identifies a potential race condition in the new waitForResponse function where the callback could be invoked multiple times, and proposes a standard and effective fix using a flag to ensure it only runs once.


[learned best practice] Use proper async handling

✅ Use proper async handling

ConnectionSerial.getDevices() is async and returns a promise per its definition; use then/catch or await instead of a callback and guard devices shape before includes.

js/protocols/stm32.js [91-103]

-ConnectionSerial.getDevices(function(devices) {
-    if (devices && devices.includes(port)) {
-        // Serial port reappeared - try to connect
+ConnectionSerial.getDevices().then(devices => {
+    if (Array.isArray(devices) && devices.includes(port)) {
         CONFIGURATOR.connection.connect(port, {bitrate: self.baud, parityBit: 'even', stopBits: 'one'}, function (openInfo) {
             if (openInfo) {
                 clearInterval(pollInterval);
                 onSuccess();
             } else {
                 GUI.connect_lock = false;
             }
         });
     }
+}).catch(() => {
+    // ignore and keep polling
 });

Suggestion importance[1-10]: 6

__

Why: Relevant best practice - Gate asynchronous flows with explicit guards and correct API usage; validate objects and await/handle promises before use.


[learned best practice] Fix listener array mismatch

✅ Fix listener array mismatch

Register receive callbacks to the correct listener array and remove from the same array to avoid broken bindings.

js/connection/connectionUdp.js [105-111]

 addOnReceiveCallback(callback){
-    this._onReceiveErrorListeners.push(callback);
+    this._onReceiveListeners.push(callback);
 }
 
 removeOnReceiveCallback(callback){
-    this._onReceiveListeners = this._onReceiveErrorListeners.filter(listener => listener !== callback);
+    this._onReceiveListeners = this._onReceiveListeners.filter(listener => listener !== callback);
 }

Suggestion importance[1-10]: 5

__

Why: Relevant best practice - Ensure event handler registration uses correct identifiers/instances to avoid mismatches.



                     PR 2416 (2025-10-19)                    
[possible issue] Hide FrSky options for non-serial receivers

✅ Hide FrSky options for non-serial receivers

Hide the #frSkyOptions section when the receiver mode is changed to a non-SERIAL type to prevent it from being displayed incorrectly.

tabs/receiver.js [86-93]

 $receiverMode.on('change', function () {
     if ($(this).find("option:selected").text() == "SERIAL") {
         $serialWrapper.show();
     } else {
         $serialWrapper.hide();
+        $("#frSkyOptions").hide();
     }
 });

Suggestion importance[1-10]: 7

__

Why: The suggestion correctly identifies a UI logic bug where the frSkyOptions section remains visible after switching from a SERIAL receiver to a non-serial one, and provides the correct fix.


[possible issue] Fix incorrect help icon association

✅ Fix incorrect help icon association

Update the for attribute on the help icon div for the fuel unit dropdown from frSkyPitchRoll to frSkyFuelUnit to correctly associate it.

tabs/receiver.html [87]

-<div for="frSkyPitchRoll" class="helpicon cf_tip" data-i18n_title="serialrx_frSkyFuelUnitHelp"></div>
+<div for="frSkyFuelUnit" class="helpicon cf_tip" data-i18n_title="serialrx_frSkyFuelUnitHelp"></div>

Suggestion importance[1-10]: 5

__

Why: The suggestion correctly identifies a copy-paste error in the for attribute of a help icon div, which should be associated with frSkyFuelUnit instead of frSkyPitchRoll.



                     PR 2414 (2025-10-15)                    
[possible issue] Correct the data unit mismatch

✅ Correct the data unit mismatch

Update the data-unit attribute for the osdPanServoPwm2decadeg input from centideg-deg to decadeg-deg. This ensures the UI sends the correct value to the firmware, matching the unit change implied by the setting's new name.

tabs/osd.html [383]

-<input id="osdPanServoPwm2decadeg" data-setting="osd_pan_servo_pwm2decadeg" data-unit="centideg-deg" data-setting-multiplier="1" type="number" data-step="1" />
+<input id="osdPanServoPwm2decadeg" data-setting="osd_pan_servo_pwm2decadeg" data-unit="decadeg-deg" data-setting-multiplier="1" type="number" data-step="1" />

Suggestion importance[1-10]: 10

__

Why: The suggestion correctly identifies a critical bug where the UI data-unit (centideg-deg) was not updated to match the renamed setting (osd_pan_servo_pwm2decadeg), which would cause incorrect values to be sent to the firmware.



                     PR 2285 (2024-12-15)                    
[possible issue] Fix UDP event binding and window guard

✅ Fix UDP event binding and window guard

Correct the UDP connection logic by attaching event listeners to the correct socket object and adding a guard to prevent calling methods on a non-window object.

js/main/udp.js [3-34]

 const socket = dgram.createSocket('udp4');
 
 const udp = {
     _id: 1,
-    _ip: false,
-    _port: false,
-    connect: function(ip, port, window = true) {
+    _ip: null,
+    _port: null,
+    connect: function(ip, port, window) {
         return new Promise(resolve => {     
             try {
-                socket.bind(port, () => {
+                socket.once('listening', () => {
                     this._ip = ip;
                     this._port = port;
+                    resolve({ error: false, id: this._id++ });
                 });
 
-                this._socket.on('error', error => {
-                    if (!window.isDestroyed()) {
+                socket.on('error', error => {
+                    if (window && typeof window.isDestroyed === 'function' && !window.isDestroyed()) {
                         window.webContents.send('udpError', error); 
                     }
                 });
 
-                this._socket.on('message', (message, _rinfo) => {
-                    if (!window.isDestroyed()) {
+                socket.on('message', (message, _rinfo) => {
+                    if (window && typeof window.isDestroyed === 'function' && !window.isDestroyed()) {
                         window.webContents.send('udpMessage', message);
                     }
                 });
-                resolve({error: false, id: this._id++});                   
+
+                socket.bind(port);
             } catch (err) {
-                resolve ({error: true, errorMsg: err});
+                resolve({ error: true, errorMsg: err });
             }
         });
     },

Suggestion importance[1-10]: 10

__

Why: The suggestion correctly identifies two critical bugs in the new udp.connect function that would cause it to fail at runtime and prevent any UDP communication.


[possible issue] Fix undefined handler target

✅ Fix undefined handler target

**

  •             * @type {ol.Pixel}
    
  •             * @private
    
  •             */
               this.coordinate_ = null;
    
  •            /**
    
  •             * @type {string|undefined}
    
  •             * @private
    
  •             */
               this.cursor_ = 'pointer';
    
  •            /**
    
  •             * @type {Feature}
    
  •             * @private
    
  •             */
               this.feature_ = null;
    
  •            /**
    
  •             * @type {string|undefined}
    
  •             * @private
    
  •             */
               this.previousCursor_ = undefined;
           }
    
  •    };
    
  •    }
    






**Fix a reference error in the Drag class constructor by correctly referencing event handlers. The app object's methods are not defined at the time of instantiation.**

[tabs/mission_control.js [2000-2014]](https://github.com/iNavFlight/inav-configurator/pull/2285/files#diff-54d84cb8c735be5909a6f1d56d93c5033b9884a1e6182fa5ac1eb619a8443786R2000-R2014)

```diff
-class Drag extends PointerInteraction{
+class Drag extends PointerInteraction {
     constructor() {
-        super ({
-            handleDownEvent: app.handleDownEvent,
-            handleDragEvent: app.handleDragEvent,
-            handleMoveEvent: app.handleMoveEvent,
-            handleUpEvent: app.handleUpEvent
+        super({
+            handleDownEvent: (evt) => this.handleDownEvent(evt),
+            handleDragEvent: (evt) => this.handleDragEvent(evt),
+            handleMoveEvent: (evt) => this.handleMoveEvent(evt),
+            handleUpEvent: (evt) => this.handleUpEvent(evt),
         });
+        this.coordinate_ = null;
+        this.cursor_ = 'pointer';
+        this.feature_ = null;
+        this.previousCursor_ = undefined;
+    }
 
-        /**
-         * @type {ol.Pixel}
-         * @private
-         */
-        this.coordinate_ = null;
-        ...
+    handleDownEvent(evt) {
+        return app.handleDownEvent(evt);
     }
-};
+    handleDragEvent(evt) {
+        return app.handleDragEvent(evt);
+    }
+    handleMoveEvent(evt) {
+        return app.handleMoveEvent(evt);
+    }
+    handleUpEvent(evt) {
+        return app.handleUpEvent(evt);
+    }
+}

Suggestion importance[1-10]: 9

__

Why: The suggestion correctly identifies that app.handle... functions are undefined when new Drag() is called, which will cause a runtime error.


[possible issue] Correct OpenLayers Feature import

✅ Correct OpenLayers Feature import

Change the import for Feature from ol/format/Feature to ol/Feature.js to correctly import the feature constructor.

js/groundstation.js [13-175]

-import Feature from 'ol/format/Feature';
+import Feature from 'ol/Feature.js';
 ...
 privateScope.cursorFeature = new Feature({
     geometry: privateScope.cursorPosition
 });

Suggestion importance[1-10]: 9

__

Why: This suggestion correctly identifies a critical bug where an incorrect module is imported, which would cause a runtime error when new Feature() is called.


[possible issue] Fix mismatched DOM id selector

✅ Fix mismatched DOM id selector

Fix the jQuery selector for the armed status icon by changing #armedicon to #armedIcon to match the element's ID in the HTML.

js/periodicStatusUpdater.js [42-48]

 if (FC.isModeEnabled('ARM')) {
-    $("#armedicon").removeClass('armed');
-    $("#armedicon").addClass('armed-active');
+    $("#armedIcon").removeClass('armed');
+    $("#armedIcon").addClass('armed-active');
 } else {
-    $("#armedicon").removeClass('armed-active');
-    $("#armedicon").addClass('armed');
+    $("#armedIcon").removeClass('armed-active');
+    $("#armedIcon").addClass('armed');
 }

Suggestion importance[1-10]: 9

__

Why: The suggestion correctly identifies a bug where the jQuery selector armedicon does not match the HTML element ID armedIcon, which would prevent the armed status icon from updating.


[possible issue] Normalize and validate icon key mapping

✅ Normalize and validate icon key mapping

**

  •             * @type {ol.Pixel}
    
  •             * @private
    
  •             */
               this.coordinate_ = null;
    
  •            /**
    
  •             * @type {string|undefined}
    
  •             * @private
    
  •             */
               this.cursor_ = 'pointer';
    
  •            /**
    
  •             * @type {Feature}
    
  •             * @private
    
  •             */
               this.feature_ = null;
    
  •            /**
    
  •             * @type {string|undefined}
    
  •             * @private
    
  •             */
               this.previousCursor_ = undefined;
           }
    
  •    };
    
  •    }
    
       app.ConvertCentimetersToMeters = function (val) {
           return parseInt(val) / 100;
    

@@ -2044,7 +2033,7 @@ var button = document.createElement('button');

             button.innerHTML = ' ';
  •            button.style = `background: url("${icons.settings_white}") no-repeat 1px -1px;background-color: rgba(0,60,136,.5);`;
    
  •            button.style = `background: url("${icons['settings_white']}") no-repeat 1px -1px;background-color: rgba(0,60,136,.5);`;
               
    
               var handleShowSettings = function () {
    

@@ -2073,7 +2062,7 @@ var button = document.createElement('button');

             button.innerHTML = ' ';
  •            button.style = `background: url("${icons.icon_safehome_white}") no-repeat 1px -1px;background-color: rgba(0,60,136,.5);`;
    
  •            button.style = `background: url("${icons['icon_safehome_white']}") no-repeat 1px -1px;background-color: rgba(0,60,136,.5);`;
               
               var handleShowSafehome = function () {
                   $('#missionPlannerSafehome').fadeIn(300);
    

@@ -2105,7 +2094,7 @@ var button = document.createElement('button');

             button.innerHTML = ' ';
  •            button.style = `background: url("${icons.icon_geozone_white}") no-repeat 1px -1px;background-color: rgba(0,60,136,.5);`;
    
  •            button.style = `background: url("${icons['icon_geozone_white']}") no-repeat 1px -1px;background-color: rgba(0,60,136,.5);`;
               
               var handleShowGeozoneSettings = function () {
                   $('#missionPlannerGeozones').fadeIn(300);
    

@@ -2138,7 +2127,7 @@ var button = document.createElement('button');

             button.innerHTML = ' ';
  •            button.style = `background: url("${icons.icon_elevation_white}") no-repeat 1px -1px;background-color: rgba(0,60,136,.5);`;
    
  •            button.style = `background: url("${icons['icon_elevation_white']}") no-repeat 1px -1px;background-color: rgba(0,60,136,.5);`;
    
               var handleShowSettings = function () {
                   $('#missionPlannerHome').fadeIn(300);
    

@@ -2171,7 +2160,7 @@ var button = document.createElement('button');

             button.innerHTML = ' ';
  •            button.style = `background: url("${icons.icon_multimission_white}") no-repeat 1px -1px;background-color: rgba(0,60,136,.5);`;
    
  •            button.style = `background: url("${icons['icon_multimission_white']}") no-repeat 1px -1px;background-color: rgba(0,60,136,.5);`;
    






**Normalize the icon key mapping to prevent broken image URLs. The dynamic import path is inconsistent with how icons are accessed, and the iconNames array contains a duplicate entry.**

[tabs/mission_control.js [70-198]](https://github.com/iNavFlight/inav-configurator/pull/2285/files#diff-54d84cb8c735be5909a6f1d56d93c5033b9884a1e6182fa5ac1eb619a8443786R70-R198)

```diff
 const iconNames = [
     'icon_mission_airplane.png',
-    ...
-    'icon_multimission_white.svg'    
+    'icon_RTH.png',
+    'icon_safehome.png',
+    'icon_safehome_used.png',
+    'icon_geozone_excl.png',
+    'icon_geozone_incl.png',
+    'icon_home.png',
+    'icon_position_edit.png',
+    'icon_position_head.png',
+    'icon_position_LDG_edit.png',
+    'icon_position_LDG.png',
+    'icon_position_PH_edit.png',
+    'icon_position_PH.png',
+    'icon_position_POI.png',
+    'icon_position_POI_edit.png',
+    'icon_position_WP_edit.png',
+    'icon_position_WP.png',
+    'icon_arrow.png',
+    'settings_white.svg',
+    'icon_safehome_white.svg',
+    'icon_geozone_white.svg',
+    'icon_elevation_white.svg',
+    'icon_multimission_white.svg'
 ];
-var icons = {};
-...
+const icons = Object.create(null);
+
+function iconKey(filename) {
+    // drop extension, keep base name (e.g., "icon_RTH")
+    return filename.replace(/\.(png|svg)$/i, '');
+}
+
 async function loadIcons() {
-    for (const icon of iconNames) {
-        const nameSplit = icon.split('.');
-        // Vites packager needs a bit help
-        var iconUrl;
-        if (nameSplit[1] == 'png') {
-            iconUrl = (await import(`./../images/icons/map/cf_${nameSplit[0]}.png?inline`)).default;
-        } else if (nameSplit[1] == 'svg') {
-            iconUrl = (await import(`./../images/icons/map/cf_${nameSplit[0]}.svg?inline`)).default;
+    for (const fname of iconNames) {
+        const base = iconKey(fname);
+        const ext = fname.split('.').pop();
+        let iconUrl;
+        if (ext === 'png') {
+            iconUrl = (await import(`./../images/icons/map/cf_${base}.png?inline`)).default;
+        } else if (ext === 'svg') {
+            iconUrl = (await import(`./../images/icons/map/cf_${base}.svg?inline`)).default;
         }
-        if (iconUrl) {
-            icons[nameSplit[0]] = iconUrl;
+        if (!iconUrl) {
+            throw new Error(`Missing icon URL for ${fname}`);
         }
+        icons[base] = iconUrl;
     }
 }
 
+// usage examples adjusted:
+// src: icons['icon_RTH']
+// src: icons['icon_position' + (suffixes...)]
+

Suggestion importance[1-10]: 8

__

Why: The suggestion correctly points out that the dynamic import path cf_${nameSplit[0]} is inconsistent with how icons are accessed later, which will lead to broken image URLs.


[possible issue] Fix async HTML/icon load order

✅ Fix async HTML/icon load order

Refactor load_html to be an async function. Await the completion of loadIcons() before calling GUI.load to ensure icons are loaded before process_html is executed.

tabs/gps.js [117-119]

-function load_html() {
-    import('./gps.html?raw').then(({default: html}) => GUI.load(html, Settings.processHtml(loadIcons().then(process_html))));
+async function load_html() {
+    const { default: html } = await import('./gps.html?raw');
+    await loadIcons();
+    GUI.load(html, Settings.processHtml(process_html));
 }

Suggestion importance[1-10]: 8

__

Why: The suggestion correctly identifies a critical race condition where process_html could execute before loadIcons completes, leading to runtime errors.


[possible issue] Prevent variable shadowing

✅ Prevent variable shadowing

Rename the model variable inside the loader.load callback to avoid shadowing the model variable from the outer scope, which holds the imported URL.

tabs/magnetometer.js [741-746]

-import(`./../resources/models/model_${model_file}.gltf`).then(({default: model}) => {
-loader.load(model, (obj) => {
-        const model = obj.scene;
+import(`./../resources/models/model_${model_file}.gltf`).then(({ default: model: modelUrl }) => {
+    loader.load(modelUrl, (obj) => {
+        const modelScene = obj.scene;
         const scaleFactor = 15;
-        model.scale.set(scaleFactor, scaleFactor, scaleFactor);
-        modelWrapper.add(model);
+        modelScene.scale.set(scaleFactor, scaleFactor, scaleFactor);
+        modelWrapper.add(modelScene);

Suggestion importance[1-10]: 4

__

Why: The suggestion correctly identifies variable shadowing which is poor practice, but the proposed improved_code contains a syntax error in the destructuring assignment.


[possible issue] Remove duplicate API property

✅ Remove duplicate API property

Remove the duplicate storeSet property from the object exposed via contextBridge to improve code quality and prevent potential future errors.

js/main/preload.js [4-10]

 contextBridge.exposeInMainWorld('electronAPI', {
   listSerialDevices: () => ipcRenderer.invoke('listSerialDevices'),
   storeGet: (key, defaultValue) => ipcRenderer.sendSync('storeGet', key, defaultValue),
   storeSet: (key, value) => ipcRenderer.send('storeSet', key, value),
-  storeSet: (key, value) => ipcRenderer.send('storeSet', key, value),
   storeDelete: (key) => ipcRenderer.send('storeDelete', key),
-  ...
+  appGetPath: (name) => ipcRenderer.sendSync('appGetPath', name),
+  appGetVersion: () => ipcRenderer.sendSync('appGetVersion'),
+  appGetLocale: () => ipcRenderer.sendSync('appGetLocale'),
+  showOpenDialog: (options) => ipcRenderer.invoke('dialog.showOpenDialog', options),
+  showSaveDialog: (options) => ipcRenderer.invoke('dialog.showSaveDialog', options),
+  alertDialog: (message) => ipcRenderer.sendSync('dialog.alert', message),
+  confirmDialog: (message) => ipcRenderer.sendSync('dialog.confirm', message),
+  tcpConnect: (host, port) => ipcRenderer.invoke('tcpConnect', host, port),
+  tcpClose: () => ipcRenderer.send('tcpClose'),
+  tcpSend: (data) => ipcRenderer.invoke('tcpSend', data),
+  onTcpError: (callback) => ipcRenderer.on('tcpError', (_event, error) => callback(error)),
+  onTcpData: (callback) => ipcRenderer.on('tcpData', (_event, data) => callback(data)),
+  onTcpEnd: (callback) => ipcRenderer.on('tcpEnd', (_event) => callback()),
+  serialConnect: (path, options) => ipcRenderer.invoke('serialConnect', path, options),
+  serialClose: () => ipcRenderer.invoke('serialClose'),
+  serialSend: (data) => ipcRenderer.invoke('serialSend', data),
+  onSerialError: (callback) => ipcRenderer.on('serialError', (_event, error) => callback(error)),
+  onSerialData: (callback) => ipcRenderer.on('serialData', (_event, data) => callback(data)),
+  onSerialClose: (callback) => ipcRenderer.on('serialClose', (_event) => callback()),
+  udpConnect: (ip, port) => ipcRenderer.invoke('udpConnect', ip, port),
+  udpClose: () => ipcRenderer.invoke('udpClose'),
+  udpSend: (data) => ipcRenderer.invoke('udpSend', data),
+  onUdpError: (callback) => ipcRenderer.on('udpError', (_event, error) => callback(error)),
+  onUdpMessage: (callback) => ipcRenderer.on('udpMessage', (_event, data) => callback(data)),
+  writeFile: (filename, data) => ipcRenderer.invoke('writeFile', filename, data),
+  readFile: (filename, encoding = 'utf8') => ipcRenderer.invoke('readFile', filename, encoding),
+  rm: (path) => ipcRenderer.invoke('rm', path),
+  chmod: (path, mode) => ipcRenderer.invoke('chmod', path, mode),
+  startChildProcess: (command, args, opts) => ipcRenderer.send('startChildProcess', command, args, opts),
+  killChildProcess: () => ipcRenderer.send('killChildProcess'),
+  onChildProcessStdout: (callback) => ipcRenderer.on('onChildProcessStdout', (_event, data) => callback(data)),
+  onChildProcessStderr: (callback) => ipcRenderer.on('onChildProcessStderr', (_event, data) => callback(data)),
+  onChildProcessError: (callback) => ipcRenderer.on('onChildProcessError', (_event, error) => callback(error)),
 });

Suggestion importance[1-10]: 3

__

Why: The suggestion correctly identifies a duplicate key storeSet which, while not causing a functional bug here, is a code quality issue that should be fixed.


[security] Sanitize user-provided profile name

✅ Sanitize user-provided profile name

Sanitize the user-provided profile name before inserting it into the DOM to prevent a potential cross-site scripting (XSS) vulnerability.

tabs/sitl.js [258-292]

 profileNewBtn_e.on('click', function () {
-    smalltalk.prompt(i18n.getMessage('sitlNewProfile'), i18n.getMessage('sitlEnterName')).then(name => {
-        if (!name)
-            return;
+    smalltalk.prompt(i18n.getMessage('sitlNewProfile'), i18n.getMessage('sitlEnterName')).then(rawName => {
+        const name = (rawName || '').trim();
+        if (!name) return;
 
-        if (profiles.find(e => { return e.name == name })) {
+        if (profiles.find(e => e.name === name)) {
             dialog.alert(i18n.getMessage('sitlProfileExists'));
             return;
         }
-        var eerpromName = name.replace(/[^a-z0-9]/gi, '_').toLowerCase() + ".bin";
-        var profile = {
-            name: name,
-            sim: "RealFlight",
+        const safeText = $('<div>').text(name).html(); // escape
+        const eepromName = name.replace(/[^a-z0-9]/gi, '_').toLowerCase() + '.bin';
+        const profile = {
+            name,
+            sim: 'RealFlight',
             isStdProfile: false,
             simEnabled: false,
-            eepromFileName: eerpromName,
+            eepromFileName: eepromName,
             port: 49001,
-            ip: "127.0.0.1",
+            ip: '127.0.0.1',
             useImu: false,
-            channelMap: [ 1, 13, 14, 0, 0, 0, 0, 0, 0, 0, 0, 0],
+            channelMap: [1, 13, 14, 0, 0, 0, 0, 0, 0, 0, 0, 0],
             useSerialReceiver: true,
             serialPort: serialPorts_e.val(),
             serialUart: 3,
-            serialProtocol: "SBus",
+            serialProtocol: 'SBus',
             baudRate: false,
             stopBits: false,
             parity: false
-        }
+        };
         profiles.push(profile);
-        profiles_e.append(`<option value="${name}">${name}</option>`)
+        profiles_e.append(`<option value="${safeText}">${safeText}</option>`);
         profiles_e.val(name);
         updateCurrentProfile();
         saveProfiles();
-    }).catch(() => {} );
+    }).catch(() => {});
 });

Suggestion importance[1-10]: 8

__

Why: The suggestion correctly identifies a cross-site scripting (XSS) vulnerability by using unescaped user input to construct HTML, which is a critical security issue.



Clone this wiki locally