Skip to content

Conversation

@Erics1337
Copy link
Contributor

@Erics1337 Erics1337 commented Dec 5, 2025

Ports Tab Vue Migration

Description

Migrated the Ports tab from legacy jQuery/HTML to a Vue 3 component PortsTab.vue.
This is part of the ongoing Vue migration (Phase 4).

Changes

  • New Component: src/components/tabs/PortsTab.vue implementing:
    • Reactive configuration table.
    • Complex dependency logic (MSP, Telemetry, Sensors, etc.).
    • Function rules validation (disabling invalid options).
    • Analytics tracking for feature usage.
  • Removed: src/js/tabs/ports.js and src/tabs/ports.html.
  • Logic:
    • Replicated functionRules mechanism for determining column content.
    • Replicated featureConfig auto-enabling logic (e.g. enabling GPS feature when GPS port is configured).
    • Ensured "AUTO" baud rates are mapped correctly during save.

Testing

  • Visual: Verify the Ports tab loads with correct table structure.
  • Functional:
    • Enable/Disable MSP on UARTs.
    • Change Baud rates and save.
    • Select Telemetry protocols and save.
    • Verify that FC.SERIAL_CONFIG is updated correctly.
    • Verify that FC.FEATURE_CONFIG is updated (e.g. enabling GPS).

Dependencies

  • Depends on BaseTab.vue (introduced in previous phase).

Summary by CodeRabbit

  • New Features

    • Redesigned Ports configuration tab with per-port controls for MSP, RX_SERIAL, telemetry, GPS, sensors, peripherals, baud-rate selection, VTX readiness indicator, and save/load flows.
  • Refactor

    • Ports UI consolidated into the Vue tab system with improved lifecycle, version-aware rule handling, and adaptive feature toggling.
  • Bug Fixes

    • Improved tab mounting/timing and polling reliability for accurate status and persistence.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 5, 2025

Walkthrough

Replaces the legacy ports tab (JS + HTML) with a Vue 3 single-file component PortsTab.vue, updates tab mounting to use the Vue mounter, adjusts tab mount lifecycle, registers the new component, and removes the old ports module and template; minor CapacitorSerial whitespace and mount-time tweaks included.

Changes

Cohort / File(s) Summary
Vue Component Implementation
src/components/tabs/PortsTab.vue
New Vue 3 SFC implementing the full ports configuration UI, reactive ports model, rule-based capability mapping (MSP, RX_SERIAL, telemetry, GPS, sensors, peripherals), baud rate pools, port-name helpers, load/save flows, feature flag updates, analytics hooks, and lifecycle management.
Component Registration & Tab Init
src/js/vue_components.js, src/js/main.js
Register PortsTab in VueTabComponents and switch ports tab startup to mountVueTab("ports", content_ready) for consistent Vue mounting.
Tab Mount Lifecycle
src/js/vue_tab_mounter.js
Ensure GUI.tab_switch_in_progress reset and optional contentReadyCallback run together inside a zero-delay timeout; switch window.vm usage to globalThis.vm when providing betaflightModel.
Legacy Ports Removal
src/js/tabs/ports.js, src/tabs/ports.html
Remove legacy vanilla-JS ports module and its HTML template (full deletion of old UI, wiring, and save/load logic).
Serial Protocol Minor Change
src/js/protocols/CapacitorSerial.js
Minor formatting change (blank line added in requestPermissionDevice); no behavior change.

Sequence Diagram(s)

mermaid
sequenceDiagram
autonumber
actor User
participant PortsTab as PortsTab.vue (UI)
participant VueApp as Vue runtime
participant MSP as MSP helper/API
participant FC as Flight Controller (EEPROM/FEATURE_CONFIG)
User->>PortsTab: Open Ports tab (mount)
PortsTab->>MSP: request VTX config, then serial config (load)
MSP->>FC: fetch config
FC-->>MSP: serial + VTX data
MSP-->>PortsTab: return config
PortsTab->>VueApp: update reactive ports state
User->>PortsTab: Modify ports, click Save
PortsTab->>MSP: write SERIAL_CONFIG and FEATURE_CONFIG, persist to EEPROM
MSP->>FC: update config, persist
FC-->>MSP: ack
MSP-->>PortsTab: save result
PortsTab->>User: show saved status / analytics event

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

  • Review focus areas:
    • src/components/tabs/PortsTab.vue — dense logic: rules system, API-version gating, feature flag derivation, load/save and MSP interactions.
    • Reconciliation of new save/load flows with removed src/js/tabs/ports.js to ensure behavioral parity.
    • src/js/vue_tab_mounter.js lifecycle change impact on contentReadyCallback timing and global tab_switch_in_progress.
    • Integration points with analytics and MSP helper calls for persistence.

Possibly related PRs

Suggested reviewers

  • haslinghuis
  • VitroidFPV
  • nerdCopter

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat: Migrate Ports Tab to Vue' directly and concisely describes the main change: migrating a UI component from jQuery/HTML to Vue 3.
Description check ✅ Passed The description comprehensively covers the changes, testing approach, and dependencies, but lacks explicit issue references (e.g., 'Fixes #XXXX') as recommended in the template.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

📜 Recent review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f38e317 and 75a55e5.

📒 Files selected for processing (1)
  • src/js/protocols/CapacitorSerial.js (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/js/protocols/CapacitorSerial.js
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: deploy

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@Erics1337 Erics1337 changed the title feat: Migrate Ports Tab to Vue (Phase 4) feat: Migrate Ports Tab to Vue Dec 5, 2025
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/js/protocols/CapacitorSerial.js (1)

53-60: Wrap hexStringToUint8Array call in try/catch to handle malformed data.

The enhanced validation in hexStringToUint8Array now throws on invalid input. If the native plugin ever sends malformed hex data, this exception will propagate uncaught and break the data receive flow.

     handleDataReceived(event) {
-        // Convert hex string to Uint8Array
-        const data = this.hexStringToUint8Array(event.data);
-        this.bytesReceived += data.length;
-
-        // Dispatch receive event with the data
-        this.dispatchEvent(new CustomEvent("receive", { detail: data }));
+        try {
+            // Convert hex string to Uint8Array
+            const data = this.hexStringToUint8Array(event.data);
+            this.bytesReceived += data.length;
+
+            // Dispatch receive event with the data
+            this.dispatchEvent(new CustomEvent("receive", { detail: data }));
+        } catch (error) {
+            console.error(`${logHead} Error parsing received data:`, error);
+        }
     }
🧹 Nitpick comments (10)
src/js/protocols/CapacitorSerial.js (1)

129-129: Add braces after if conditions for consistency.

Static analysis flags missing braces on early-return if statements. While these work correctly, adding braces improves readability and aligns with typical style guidelines.

Example fix for one instance:

-        if (!this.pluginAvailable) return;
+        if (!this.pluginAvailable) {
+            return;
+        }

Apply similar changes at lines 143, 167, 242, and 267-270.

Also applies to: 143-143, 167-167, 242-242, 267-270

src/js/main.js (1)

301-304: Consider consistent commenting style.

The servos case has a comment explaining the Vue tab approach, but the ports case (line 264) doesn't. For consistency, either add a similar comment to ports or remove the comment from servos.

src/js/vue_components.js (1)

25-27: Consider if global registration is necessary for tab components.

Tab components are dynamically mounted via VueTabComponents registry in vue_tab_mounter.js, so they may not need global registration in BetaflightComponents. However, keeping them registered globally allows using <ServosTab> or <PortsTab> directly in templates if needed in the future.

src/components/tabs/ServosTab.vue (2)

17-17: Replace deprecated width attribute with CSS.

The width attribute on <th> is deprecated in HTML5. Use CSS instead.

-                                    <th width="110px">{{ $t("servosName") }}</th>
+                                    <th style="width: 110px">{{ $t("servosName") }}</th>

165-168: Consider making rateOptions a computed or top-level constant.

rateOptions is built once in setup and never changes. Moving it outside setup() as a module-level constant would be cleaner and avoid recreating the array on each component instantiation.

+// Generate rate options from 100 to -100 (module-level constant)
+const RATE_OPTIONS = Array.from({ length: 201 }, (_, i) => 100 - i);
+
 export default defineComponent({
     name: "ServosTab",
     components: {
         BaseTab,
     },
     setup() {
         // ...
-        // Generate rate options from 100 to -100
-        const rateOptions = [];
-        for (let i = 100; i > -101; i--) {
-            rateOptions.push(i);
-        }
+        const rateOptions = RATE_OPTIONS;
src/components/tabs/PortsTab.vue (5)

47-55: Form labels lack visible text for accessibility.

The <label> elements are empty and rely on CSS for visual indication. While the for attribute correctly associates them with inputs, screen readers need accessible text.

                                     <input
                                         type="checkbox"
                                         class="togglemedium"
                                         :id="`msp-${index}`"
                                         v-model="port.msp"
                                         :disabled="port.identifier === 20"
                                     />
-                                    <label :for="`msp-${index}`"></label>
+                                    <label :for="`msp-${index}`">
+                                        <span class="sr-only">{{ $t("portsMSP") }}</span>
+                                    </label>

Apply the same pattern for the Serial RX checkbox label at line 70.

Also applies to: 64-71


323-335: Refactor nested callbacks using async/await.

The nested callback structure exceeds 4 levels and can be simplified. Since MSP.promise() is already used on line 324, extend its usage.

-        const loadConfig = () => {
-            MSP.promise(MSPCodes.MSP_VTX_CONFIG).then(() => {
-                mspHelper.loadSerialConfig(() => {
-                    ports.length = 0;
-                    FC.SERIAL_CONFIG.ports.forEach((p) => {
-                        ports.push(transformPortData(p));
-                    });
-                    nextTick(() => {
-                        GUI.content_ready();
-                    });
-                });
-            });
-        };
+        const loadConfig = async () => {
+            await MSP.promise(MSPCodes.MSP_VTX_CONFIG);
+            await new Promise((resolve) => mspHelper.loadSerialConfig(resolve));
+            
+            ports.length = 0;
+            FC.SERIAL_CONFIG.ports.forEach((p) => {
+                ports.push(transformPortData(p));
+            });
+            
+            await nextTick();
+            GUI.content_ready();
+        };

391-403: Deep callback nesting in saveConfig.

The save flow has 4+ levels of nesting. Consider using async/await for clarity.

-            mspHelper.sendSerialConfig(() => {
-                MSP.send_message(
-                    MSPCodes.MSP_SET_FEATURE_CONFIG,
-                    mspHelper.crunch(MSPCodes.MSP_SET_FEATURE_CONFIG),
-                    false,
-                    () => {
-                        mspHelper.writeConfiguration(true, () => {
-                            gui_log(i18n.getMessage("portsEepromSave"));
-                        });
-                    },
-                );
-            });
+            await new Promise((resolve) => mspHelper.sendSerialConfig(resolve));
+            await MSP.promise(
+                MSPCodes.MSP_SET_FEATURE_CONFIG,
+                mspHelper.crunch(MSPCodes.MSP_SET_FEATURE_CONFIG),
+            );
+            await new Promise((resolve) => mspHelper.writeConfiguration(true, resolve));
+            gui_log(i18n.getMessage("portsEepromSave"));

This requires making saveConfig an async function.


405-428: Reduce cognitive complexity in updateFeatures.

This function exceeds the allowed cognitive complexity (16 vs 15). Refactor using a mapping approach.

         const updateFeatures = () => {
-            // Logic from original ports.js lines 440-498
-            let enableRxSerial = false;
-            let enableTelemetry = false;
-            let enableBlackbox = false;
-            let enableEsc = false;
-            let enableGps = false;
-
-            for (const port of FC.SERIAL_CONFIG.ports) {
-                const func = port.functions;
-                if (func.includes("RX_SERIAL")) enableRxSerial = true;
-                if (func.some((e) => e.startsWith("TELEMETRY"))) enableTelemetry = true;
-                if (func.includes("BLACKBOX")) enableBlackbox = true;
-                if (func.includes("ESC_SENSOR")) enableEsc = true;
-                if (func.includes("GPS")) enableGps = true;
-            }
-
-            const featureConfig = FC.FEATURE_CONFIG.features;
-            enableRxSerial ? featureConfig.enable("RX_SERIAL") : featureConfig.disable("RX_SERIAL");
-            if (enableTelemetry) featureConfig.enable("TELEMETRY"); // Note: never disabled in original?
-            enableBlackbox ? featureConfig.enable("BLACKBOX") : featureConfig.disable("BLACKBOX");
-            enableEsc ? featureConfig.enable("ESC_SENSOR") : featureConfig.disable("ESC_SENSOR");
-            enableGps ? featureConfig.enable("GPS") : featureConfig.disable("GPS");
+            const allFunctions = FC.SERIAL_CONFIG.ports.flatMap((p) => p.functions);
+            const featureConfig = FC.FEATURE_CONFIG.features;
+
+            const featureMap = {
+                RX_SERIAL: allFunctions.includes("RX_SERIAL"),
+                BLACKBOX: allFunctions.includes("BLACKBOX"),
+                ESC_SENSOR: allFunctions.includes("ESC_SENSOR"),
+                GPS: allFunctions.includes("GPS"),
+            };
+
+            for (const [feature, enabled] of Object.entries(featureMap)) {
+                enabled ? featureConfig.enable(feature) : featureConfig.disable(feature);
+            }
+
+            // TELEMETRY is only enabled, never disabled (per original behavior)
+            if (allFunctions.some((f) => f.startsWith("TELEMETRY"))) {
+                featureConfig.enable("TELEMETRY");
+            }
         };

297-303: Potential false positive for disabled rules when buildOptions is empty.

When FC.CONFIG.buildOptions.length is 0 (falsy), the function returns false, meaning all rules with dependsOn are enabled. This is correct behavior for builds without build options, but the logic is slightly confusing.

Consider making the intent clearer:

         const isRuleDisabled = (rule) => {
-            return (
-                FC.CONFIG.buildOptions.length &&
-                rule.dependsOn !== undefined &&
-                !FC.CONFIG.buildOptions.includes(rule.dependsOn)
-            );
+            // Rules are enabled if: no build options exist, rule has no dependency, or dependency is met
+            if (!FC.CONFIG.buildOptions.length || rule.dependsOn === undefined) {
+                return false;
+            }
+            return !FC.CONFIG.buildOptions.includes(rule.dependsOn);
         };
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 41cb8c1 and df5d0a6.

📒 Files selected for processing (11)
  • src/components/tabs/BaseTab.vue (1 hunks)
  • src/components/tabs/PortsTab.vue (1 hunks)
  • src/components/tabs/ServosTab.vue (1 hunks)
  • src/js/main.js (3 hunks)
  • src/js/protocols/CapacitorSerial.js (8 hunks)
  • src/js/tabs/ports.js (0 hunks)
  • src/js/tabs/servos.js (0 hunks)
  • src/js/vue_components.js (2 hunks)
  • src/js/vue_tab_mounter.js (1 hunks)
  • src/tabs/ports.html (0 hunks)
  • src/tabs/servos.html (0 hunks)
💤 Files with no reviewable changes (4)
  • src/js/tabs/ports.js
  • src/tabs/servos.html
  • src/js/tabs/servos.js
  • src/tabs/ports.html
🧰 Additional context used
🧠 Learnings (13)
📚 Learning: 2025-06-20T12:35:49.283Z
Learnt from: mituritsyn
Repo: betaflight/betaflight-configurator PR: 4526
File: src/js/gui.js:43-43
Timestamp: 2025-06-20T12:35:49.283Z
Learning: In the Betaflight Configurator codebase, tabs in `defaultCloudBuildTabOptions` are conditionally displayed based on firmware build options. The logic in `serial_backend.js` checks `FC.CONFIG.buildOptions` and only adds tabs to `GUI.allowedTabs` if the firmware was built with support for that feature.

Applied to files:

  • src/components/tabs/ServosTab.vue
  • src/js/vue_components.js
  • src/components/tabs/PortsTab.vue
📚 Learning: 2025-09-10T18:26:10.136Z
Learnt from: haslinghuis
Repo: betaflight/betaflight-configurator PR: 4601
File: package.json:125-125
Timestamp: 2025-09-10T18:26:10.136Z
Learning: In betaflight-configurator, dependency updates are handled incrementally - Vue dependencies are updated separately from Vite dependencies for better isolation and maintainability.

Applied to files:

  • src/js/vue_components.js
📚 Learning: 2025-12-01T17:09:13.412Z
Learnt from: haslinghuis
Repo: betaflight/betaflight-configurator PR: 0
File: :0-0
Timestamp: 2025-12-01T17:09:13.412Z
Learning: In betaflight-configurator’s CapacitorBluetooth.js, native plugin listeners (notification, services, connectionState) must be attached exactly once per app lifetime. Re-attaching them during writes/connect cycles leads to duplicate handlers, increased JS workload, MSP queue pressure, and can prevent the MSP_EEPROM_WRITE callback from firing (so reinitializeConnection is not reached). Implement an idempotent _attachNativeListenersOnce() guard.

Applied to files:

  • src/js/protocols/CapacitorSerial.js
📚 Learning: 2025-11-24T15:07:25.227Z
Learnt from: haslinghuis
Repo: betaflight/betaflight-configurator PR: 4706
File: src/js/protocols/CapacitorSerial.js:47-47
Timestamp: 2025-11-24T15:07:25.227Z
Learning: In betaflight-configurator's CapacitorSerial protocol (src/js/protocols/CapacitorSerial.js), the fire-and-forget this.loadDevices() call in the constructor is intentional background pre-loading. The actual critical device list loading for UI population is orchestrated by port_handler.js via serial.getDevices("serial"), which properly awaits CapacitorSerial.getDevices() → loadDevices() in a complete async chain. The constructor's async call does not cause race conditions.

Applied to files:

  • src/js/protocols/CapacitorSerial.js
📚 Learning: 2025-11-28T22:41:59.374Z
Learnt from: haslinghuis
Repo: betaflight/betaflight-configurator PR: 4714
File: src/js/protocols/CapacitorBluetooth.js:135-143
Timestamp: 2025-11-28T22:41:59.374Z
Learning: In betaflight-configurator, WebSerial.js uses a constant path "serial" for all devices, and CapacitorBluetooth.js uses constant path "bluetooth", consistent with the single-device-at-a-time workflow. WebBluetooth.js uses counter-based paths (bluetooth_${counter}), but that's specific to its implementation. The path strategy varies by protocol based on their specific requirements.

Applied to files:

  • src/js/protocols/CapacitorSerial.js
📚 Learning: 2025-11-28T22:51:11.691Z
Learnt from: haslinghuis
Repo: betaflight/betaflight-configurator PR: 4714
File: src/js/protocols/CapacitorBluetooth.js:176-206
Timestamp: 2025-11-28T22:51:11.691Z
Learning: In betaflight-configurator, Web Bluetooth API's `services` option filters scan results to only devices advertising those UUIDs, while `optionalServices` requests permission to access services after connection without filtering. PWA (WebBluetooth.js) uses `{ acceptAllDevices: true, optionalServices: uuids }` to show all devices while requesting post-connection service access. CapacitorBluetooth.js should match this pattern rather than using `services` as a scan filter.

Applied to files:

  • src/js/protocols/CapacitorSerial.js
📚 Learning: 2025-10-25T21:16:32.474Z
Learnt from: haslinghuis
Repo: betaflight/betaflight-configurator PR: 4379
File: src/js/protocols/TauriSerial.js:203-259
Timestamp: 2025-10-25T21:16:32.474Z
Learning: In TauriSerial (src/js/protocols/TauriSerial.js), the requestPermissionDevice() method is not needed and not invoked. Tauri automatically discovers serial devices through the constructor's loadDevices() and startDeviceMonitoring() calls, bypassing the browser permission model that WebSerial requires. Devices are auto-detected via a 1-second polling interval without user permission prompts.

Applied to files:

  • src/js/protocols/CapacitorSerial.js
📚 Learning: 2025-11-22T21:18:08.814Z
Learnt from: haslinghuis
Repo: betaflight/betaflight-configurator PR: 4706
File: src/js/protocols/CapacitorSerial.js:11-46
Timestamp: 2025-11-22T21:18:08.814Z
Learning: In betaflight-configurator, protocol implementations (WebSerial, CapacitorSerial, WebBluetooth, etc.) are instantiated once in the Serial class constructor and stored as singletons in the _protocols array for the entire application lifetime. They are never destroyed or recreated, so cleanup methods to remove event listeners are not needed.

Applied to files:

  • src/js/protocols/CapacitorSerial.js
📚 Learning: 2025-08-22T16:43:20.901Z
Learnt from: haslinghuis
Repo: betaflight/betaflight-configurator PR: 4576
File: src/js/port_usage.js:17-23
Timestamp: 2025-08-22T16:43:20.901Z
Learning: In betaflight-configurator, the serial facade architecture requires accessing metrics like bitrate, bytesReceived, and bytesSent from serial._protocol rather than the top-level serial object. This change maintains compatibility with existing port utilization calculations that have been stable for over 11 years.

Applied to files:

  • src/js/protocols/CapacitorSerial.js
📚 Learning: 2025-06-19T22:13:09.136Z
Learnt from: blckmn
Repo: betaflight/betaflight-configurator PR: 4521
File: src/js/protocols/WebSerial.js:148-151
Timestamp: 2025-06-19T22:13:09.136Z
Learning: In WebSerial.js, there's a timing issue where the cached `this.ports` array doesn't immediately reflect newly permitted devices after `requestPermissionDevice()` completes. The `getDevices()` method needs to refresh the device list from the browser API to return accurate data immediately following a permission request and user acceptance.

Applied to files:

  • src/js/protocols/CapacitorSerial.js
📚 Learning: 2025-11-06T17:26:50.201Z
Learnt from: haslinghuis
Repo: betaflight/betaflight-configurator PR: 4687
File: src/js/protocols/TauriSerial.js:299-310
Timestamp: 2025-11-06T17:26:50.201Z
Learning: In tauri-plugin-serialplugin Android development, the Android project must be initialized via `tauri android init` before the USB support patches (AndroidManifest.xml permissions, device_filter.xml, MainActivity.kt) can be applied. Without initialization, the plugin returns empty results from available_ports_direct() because the generated Android project structure and USB permissions don't exist yet.

Applied to files:

  • src/js/protocols/CapacitorSerial.js
📚 Learning: 2025-06-27T22:06:49.210Z
Learnt from: haslinghuis
Repo: betaflight/betaflight-configurator PR: 4532
File: src/js/VirtualFC.js:234-234
Timestamp: 2025-06-27T22:06:49.210Z
Learning: In the betaflight-configurator codebase, the VirtualFC.js AUX_CONFIG array must stay synchronized with the betaflight firmware's msp_box.c definitions to ensure proper auxiliary mode functionality. Changes to mode names should follow the firmware source code even if it potentially breaks backward compatibility with saved presets.

Applied to files:

  • src/components/tabs/PortsTab.vue
📚 Learning: 2025-09-19T20:42:20.332Z
Learnt from: haslinghuis
Repo: betaflight/betaflight-configurator PR: 4510
File: src/js/msp.js:384-391
Timestamp: 2025-09-19T20:42:20.332Z
Learning: Complex MSP duplicate handling fixes in Betaflight Configurator can cause infinite loading messages when changing tabs due to disruption of the callback resolution mechanism. Simple code-only duplicate detection (using this.callbacks.some((instance) => instance.code === code)) is the safer approach that preserves tab switching functionality.

Applied to files:

  • src/components/tabs/PortsTab.vue
🧬 Code graph analysis (3)
src/js/main.js (1)
src/js/vue_tab_mounter.js (1)
  • mountVueTab (31-80)
src/js/vue_tab_mounter.js (2)
src/js/vue_components.js (2)
  • VueTabComponents (11-14)
  • VueTabComponents (11-14)
src/js/gui.js (1)
  • GUI (473-473)
src/js/vue_components.js (2)
src/components/init.js (1)
  • app (60-64)
src/js/vue_loader.js (1)
  • app (42-42)
🪛 GitHub Check: SonarCloud Code Analysis
src/js/vue_tab_mounter.js

[warning] 63-63: Prefer globalThis over window.

See more on https://sonarcloud.io/project/issues?id=betaflight_betaflight-configurator&issues=AZrwUfA3smiZw1ZhZMVu&open=AZrwUfA3smiZw1ZhZMVu&pullRequest=4726


[warning] 62-62: Prefer globalThis over window.

See more on https://sonarcloud.io/project/issues?id=betaflight_betaflight-configurator&issues=AZrwUfA3smiZw1ZhZMVt&open=AZrwUfA3smiZw1ZhZMVt&pullRequest=4726

src/components/tabs/ServosTab.vue

[warning] 6-6: Anchors must have content and the content must be accessible by a screen reader.

See more on https://sonarcloud.io/project/issues?id=betaflight_betaflight-configurator&issues=AZrwUfBvsmiZw1ZhZMV0&open=AZrwUfBvsmiZw1ZhZMV0&pullRequest=4726


[warning] 17-17: Remove this deprecated "width" attribute.

See more on https://sonarcloud.io/project/issues?id=betaflight_betaflight-configurator&issues=AZrwUfBvsmiZw1ZhZMV1&open=AZrwUfBvsmiZw1ZhZMV1&pullRequest=4726


[failure] 255-255: Refactor this code to not nest functions more than 4 levels deep.

See more on https://sonarcloud.io/project/issues?id=betaflight_betaflight-configurator&issues=AZrwUfBvsmiZw1ZhZMV3&open=AZrwUfBvsmiZw1ZhZMV3&pullRequest=4726


[warning] 171-171: Move function 'getBarStyle' to the outer scope.

See more on https://sonarcloud.io/project/issues?id=betaflight_betaflight-configurator&issues=AZrwUfBvsmiZw1ZhZMV2&open=AZrwUfBvsmiZw1ZhZMV2&pullRequest=4726

src/js/protocols/CapacitorSerial.js

[failure] 129-129: Expected { after 'if' condition.

See more on https://sonarcloud.io/project/issues?id=betaflight_betaflight-configurator&issues=AZrwUfBWsmiZw1ZhZMVv&open=AZrwUfBWsmiZw1ZhZMVv&pullRequest=4726


[failure] 143-143: Expected { after 'if' condition.

See more on https://sonarcloud.io/project/issues?id=betaflight_betaflight-configurator&issues=AZrwUfBWsmiZw1ZhZMVw&open=AZrwUfBWsmiZw1ZhZMVw&pullRequest=4726


[failure] 167-167: Expected { after 'if' condition.

See more on https://sonarcloud.io/project/issues?id=betaflight_betaflight-configurator&issues=AZrwUfBWsmiZw1ZhZMVx&open=AZrwUfBWsmiZw1ZhZMVx&pullRequest=4726


[failure] 242-242: Expected { after 'if' condition.

See more on https://sonarcloud.io/project/issues?id=betaflight_betaflight-configurator&issues=AZrwUfBWsmiZw1ZhZMVy&open=AZrwUfBWsmiZw1ZhZMVy&pullRequest=4726


[failure] 268-268: Expected { after 'if' condition.

See more on https://sonarcloud.io/project/issues?id=betaflight_betaflight-configurator&issues=AZrwUfBWsmiZw1ZhZMVz&open=AZrwUfBWsmiZw1ZhZMVz&pullRequest=4726

src/components/tabs/PortsTab.vue

[failure] 417-417: Expected { after 'if' condition.

See more on https://sonarcloud.io/project/issues?id=betaflight_betaflight-configurator&issues=AZrwUfB8smiZw1ZhZMWG&open=AZrwUfB8smiZw1ZhZMWG&pullRequest=4726


[failure] 419-419: Expected { after 'if' condition.

See more on https://sonarcloud.io/project/issues?id=betaflight_betaflight-configurator&issues=AZrwUfB8smiZw1ZhZMWI&open=AZrwUfB8smiZw1ZhZMWI&pullRequest=4726


[failure] 397-397: Refactor this code to not nest functions more than 4 levels deep.

See more on https://sonarcloud.io/project/issues?id=betaflight_betaflight-configurator&issues=AZrwUfB8smiZw1ZhZMWC&open=AZrwUfB8smiZw1ZhZMWC&pullRequest=4726


[failure] 369-369: Expected { after 'if' condition.

See more on https://sonarcloud.io/project/issues?id=betaflight_betaflight-configurator&issues=AZrwUfB8smiZw1ZhZMV9&open=AZrwUfB8smiZw1ZhZMV9&pullRequest=4726


[warning] 54-54: A form label must be associated with a control.

See more on https://sonarcloud.io/project/issues?id=betaflight_betaflight-configurator&issues=AZrwUfB8smiZw1ZhZMV5&open=AZrwUfB8smiZw1ZhZMV5&pullRequest=4726


[failure] 371-371: Expected { after 'if' condition.

See more on https://sonarcloud.io/project/issues?id=betaflight_betaflight-configurator&issues=AZrwUfB8smiZw1ZhZMV_&open=AZrwUfB8smiZw1ZhZMV_&pullRequest=4726


[failure] 327-327: Refactor this code to not nest functions more than 4 levels deep.

See more on https://sonarcloud.io/project/issues?id=betaflight_betaflight-configurator&issues=AZrwUfB8smiZw1ZhZMV7&open=AZrwUfB8smiZw1ZhZMV7&pullRequest=4726


[failure] 370-370: Expected { after 'if' condition.

See more on https://sonarcloud.io/project/issues?id=betaflight_betaflight-configurator&issues=AZrwUfB8smiZw1ZhZMV-&open=AZrwUfB8smiZw1ZhZMV-&pullRequest=4726


[failure] 424-424: Expected { after 'if' condition.

See more on https://sonarcloud.io/project/issues?id=betaflight_betaflight-configurator&issues=AZrwUfB8smiZw1ZhZMWJ&open=AZrwUfB8smiZw1ZhZMWJ&pullRequest=4726


[failure] 372-372: Expected { after 'if' condition.

See more on https://sonarcloud.io/project/issues?id=betaflight_betaflight-configurator&issues=AZrwUfB8smiZw1ZhZMWA&open=AZrwUfB8smiZw1ZhZMWA&pullRequest=4726


[failure] 373-373: Expected { after 'if' condition.

See more on https://sonarcloud.io/project/issues?id=betaflight_betaflight-configurator&issues=AZrwUfB8smiZw1ZhZMWB&open=AZrwUfB8smiZw1ZhZMWB&pullRequest=4726


[failure] 415-415: Expected { after 'if' condition.

See more on https://sonarcloud.io/project/issues?id=betaflight_betaflight-configurator&issues=AZrwUfB8smiZw1ZhZMWE&open=AZrwUfB8smiZw1ZhZMWE&pullRequest=4726


[failure] 405-405: Refactor this function to reduce its Cognitive Complexity from 16 to the 15 allowed.

See more on https://sonarcloud.io/project/issues?id=betaflight_betaflight-configurator&issues=AZrwUfB8smiZw1ZhZMWD&open=AZrwUfB8smiZw1ZhZMWD&pullRequest=4726


[failure] 418-418: Expected { after 'if' condition.

See more on https://sonarcloud.io/project/issues?id=betaflight_betaflight-configurator&issues=AZrwUfB8smiZw1ZhZMWH&open=AZrwUfB8smiZw1ZhZMWH&pullRequest=4726


[warning] 70-70: A form label must be associated with a control.

See more on https://sonarcloud.io/project/issues?id=betaflight_betaflight-configurator&issues=AZrwUfB8smiZw1ZhZMV6&open=AZrwUfB8smiZw1ZhZMV6&pullRequest=4726


[failure] 443-443: Expected { after 'if' condition.

See more on https://sonarcloud.io/project/issues?id=betaflight_betaflight-configurator&issues=AZrwUfB8smiZw1ZhZMWK&open=AZrwUfB8smiZw1ZhZMWK&pullRequest=4726


[failure] 330-330: Refactor this code to not nest functions more than 4 levels deep.

See more on https://sonarcloud.io/project/issues?id=betaflight_betaflight-configurator&issues=AZrwUfB8smiZw1ZhZMV8&open=AZrwUfB8smiZw1ZhZMV8&pullRequest=4726


[warning] 6-6: Anchors must have content and the content must be accessible by a screen reader.

See more on https://sonarcloud.io/project/issues?id=betaflight_betaflight-configurator&issues=AZrwUfB8smiZw1ZhZMV4&open=AZrwUfB8smiZw1ZhZMV4&pullRequest=4726


[failure] 416-416: Expected { after 'if' condition.

See more on https://sonarcloud.io/project/issues?id=betaflight_betaflight-configurator&issues=AZrwUfB8smiZw1ZhZMWF&open=AZrwUfB8smiZw1ZhZMWF&pullRequest=4726

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
🔇 Additional comments (14)
src/js/protocols/CapacitorSerial.js (2)

312-325: Good addition of input validation.

The 0x prefix stripping, character validation, and even-length check provide robust input handling for hex string conversion.


15-48: LGTM!

The pluginAvailable guard pattern is a good defensive measure that prevents native plugin calls when the plugin is unavailable, ensuring graceful degradation on unsupported platforms.

src/components/tabs/BaseTab.vue (2)

32-49: LGTM!

The setup correctly manages tab lifecycle: setting GUI.active_tab on mount and cleaning up intervals/timeouts on unmount. The betaflightModel injection with null fallback is appropriate.


1-5: Clean template structure.

The dynamic class binding tab-${tabName} follows the existing tab naming convention and the slot pattern enables flexible content composition.

src/js/main.js (1)

263-265: LGTM!

The Vue tab mounting integration for the ports tab is clean and maintains compatibility with the existing content_ready callback pattern.

src/js/vue_components.js (1)

10-14: LGTM!

The VueTabComponents registry provides a clean lookup mechanism for dynamic tab mounting in vue_tab_mounter.js.

src/js/vue_tab_mounter.js (3)

31-80: LGTM! Well-structured tab mounting utility.

The implementation correctly:

  • Validates component existence and target element
  • Cleans up previous tab before mounting new one
  • Configures i18n and provides the betaflight model
  • Defers callback execution to ensure Vue has completed mounting

85-89: LGTM!

The unmountVueTab cleanup function properly handles the singleton app instance and null checks.


61-64: Consider using globalThis instead of window.

Static analysis suggests preferring globalThis over window for broader compatibility (e.g., web workers, SSR). However, if the codebase consistently uses window.vm elsewhere, maintaining consistency may be more important.

     // Provide the global betaflight model
-    if (window.vm) {
-        currentTabApp.provide("betaflightModel", window.vm);
+    if (globalThis.vm) {
+        currentTabApp.provide("betaflightModel", globalThis.vm);
     }
src/components/tabs/ServosTab.vue (3)

29-74: LGTM!

The servo configuration table implementation is well-structured with proper v-for loops, reactive bindings, and event handlers. The channel forwarding checkbox logic using :checked and @change correctly implements radio-like behavior within each row.


302-306: LGTM!

Proper cleanup of locally tracked intervals in onUnmounted. This prevents memory leaks and ensures the component doesn't leave orphaned interval handlers.


253-261: Nested callback structure is consistent with codebase patterns.

The 4-level nested callbacks follow the established callback-based MSP pattern used throughout betaflight-configurator. The sequential dependency between these MSP calls (servo configurations → servo mix rules → RC → box names → UI initialization) is appropriately expressed through nested callbacks. MSP.promise() is not available in this codebase, so async/await refactoring is not applicable here. The current structure is the correct approach for this context.

src/components/tabs/PortsTab.vue (2)

168-219: LGTM!

The functionRules array is well-structured with clear definitions for groups, max ports, sharing rules, and firmware dependencies. The conditional rule addition for API version 1.45+ and localization loop are correctly implemented.


252-275: Port identifier mapping uses separate ranges for different hardware platforms.

Identifiers 1-7 and 51-57 map to overlapping UART names for different MCU peripheral types or flight controller variants. This aligns with Betaflight's multi-platform architecture but could benefit from clarification in comments about which ranges apply to which hardware families.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (4)
src/components/tabs/PortsTab.vue (4)

42-162: Consider restricting non‑MSP functions on USB VCP for parity with legacy UI.

Right now only the MSP checkbox is disabled for identifier === 20 (USB VCP); Serial RX, telemetry, sensors, peripherals, and baudrate selects remain fully editable. On older Configurator builds, USB VCP was typically MSP‑only and other functions weren’t assignable on that port.

If legacy ports.js also prevented configuring non‑MSP functions on VCP, mirroring that here (e.g., disabling the RX checkbox and the select elements when port.identifier === 20) would avoid confusing users with options that the FC can’t meaningfully use.


198-261: functionRules metadata is only partially used; either wire it in or trim for clarity.

maxPorts, sharableWith, and notSharableWith are defined on many rules, but the current logic only consults dependsOn via isRuleDisabled(). If you’re not yet enforcing maxPorts or sharing constraints, this metadata is effectively dead and can confuse future readers.

Either:

  • Implement the remaining rule logic (e.g., to disable options once maxPorts is reached, or enforce sharing constraints), or
  • Drop the unused properties for now and reintroduce them when the corresponding behavior is ported.

This keeps the rules table aligned with the actual behavior.


354-381: Add basic error handling around MSP_VTX_CONFIG load to avoid stuck “loading” states.

loadConfig() only proceeds on MSP.promise(MSPCodes.MSP_VTX_CONFIG).then(...); if that promise rejects (e.g., older firmware or transient MSP error), mspHelper.loadSerialConfig and GUI.content_ready() are never called, so the Ports tab may appear to hang.

Consider adding a .catch/.finally to either:

  • Log the failure and still call mspHelper.loadSerialConfig(handleSerialConfigLoaded), or
  • At least call GUI.content_ready() so the tab becomes usable even if VTX config isn’t available.

That keeps the tab resilient when VTX support is missing or misconfigured.


450-468: Add braces to getEnabledFeaturesFromPorts if statements (and satisfy Sonar).

The single‑line if statements in getEnabledFeaturesFromPorts are functionally correct but violate the project’s brace style and match the Sonar “Expected { after 'if' condition” warnings.

You can fix this and improve readability with:

             for (const port of ports) {
                 const func = port.functions;
-                if (func.includes("RX_SERIAL")) flags.rxSerial = true;
-                if (func.some((e) => e.startsWith("TELEMETRY"))) flags.telemetry = true;
-                if (func.includes("BLACKBOX")) flags.blackbox = true;
-                if (func.includes("ESC_SENSOR")) flags.esc = true;
-                if (func.includes("GPS")) flags.gps = true;
+                if (func.includes("RX_SERIAL")) {
+                    flags.rxSerial = true;
+                }
+                if (func.some((e) => e.startsWith("TELEMETRY"))) {
+                    flags.telemetry = true;
+                }
+                if (func.includes("BLACKBOX")) {
+                    flags.blackbox = true;
+                }
+                if (func.includes("ESC_SENSOR")) {
+                    flags.esc = true;
+                }
+                if (func.includes("GPS")) {
+                    flags.gps = true;
+                }
             }

This should clear the static analysis findings and keep the style consistent with the nearby saveConfig mapping.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between df5d0a6 and 60c6607.

📒 Files selected for processing (4)
  • src/components/tabs/PortsTab.vue (1 hunks)
  • src/components/tabs/ServosTab.vue (1 hunks)
  • src/js/protocols/CapacitorSerial.js (8 hunks)
  • src/js/vue_tab_mounter.js (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/js/protocols/CapacitorSerial.js
  • src/components/tabs/ServosTab.vue
🧰 Additional context used
🧠 Learnings (8)
📓 Common learnings
Learnt from: haslinghuis
Repo: betaflight/betaflight-configurator PR: 4510
File: src/js/msp.js:384-391
Timestamp: 2025-09-19T20:42:20.332Z
Learning: Complex MSP duplicate handling fixes in Betaflight Configurator can cause infinite loading messages when changing tabs due to disruption of the callback resolution mechanism. Simple code-only duplicate detection (using this.callbacks.some((instance) => instance.code === code)) is the safer approach that preserves tab switching functionality.
📚 Learning: 2025-09-19T20:42:20.332Z
Learnt from: haslinghuis
Repo: betaflight/betaflight-configurator PR: 4510
File: src/js/msp.js:384-391
Timestamp: 2025-09-19T20:42:20.332Z
Learning: Complex MSP duplicate handling fixes in Betaflight Configurator can cause infinite loading messages when changing tabs due to disruption of the callback resolution mechanism. Simple code-only duplicate detection (using this.callbacks.some((instance) => instance.code === code)) is the safer approach that preserves tab switching functionality.

Applied to files:

  • src/components/tabs/PortsTab.vue
📚 Learning: 2025-09-19T20:41:44.286Z
Learnt from: haslinghuis
Repo: betaflight/betaflight-configurator PR: 4510
File: src/js/msp.js:384-391
Timestamp: 2025-09-19T20:41:44.286Z
Learning: When fixing MSP duplicate handling in Betaflight Configurator, avoid complex changes to callback resolution mechanisms as they can break tab switching functionality. Simple duplicate detection based on code and payload size is safer than complex requestKey-based approaches.

Applied to files:

  • src/components/tabs/PortsTab.vue
📚 Learning: 2025-06-20T12:35:49.283Z
Learnt from: mituritsyn
Repo: betaflight/betaflight-configurator PR: 4526
File: src/js/gui.js:43-43
Timestamp: 2025-06-20T12:35:49.283Z
Learning: In the Betaflight Configurator codebase, tabs in `defaultCloudBuildTabOptions` are conditionally displayed based on firmware build options. The logic in `serial_backend.js` checks `FC.CONFIG.buildOptions` and only adds tabs to `GUI.allowedTabs` if the firmware was built with support for that feature.

Applied to files:

  • src/components/tabs/PortsTab.vue
📚 Learning: 2025-06-27T22:06:49.210Z
Learnt from: haslinghuis
Repo: betaflight/betaflight-configurator PR: 4532
File: src/js/VirtualFC.js:234-234
Timestamp: 2025-06-27T22:06:49.210Z
Learning: In the betaflight-configurator codebase, the VirtualFC.js AUX_CONFIG array must stay synchronized with the betaflight firmware's msp_box.c definitions to ensure proper auxiliary mode functionality. Changes to mode names should follow the firmware source code even if it potentially breaks backward compatibility with saved presets.

Applied to files:

  • src/components/tabs/PortsTab.vue
📚 Learning: 2025-06-09T00:32:21.385Z
Learnt from: haslinghuis
Repo: betaflight/betaflight-configurator PR: 0
File: :0-0
Timestamp: 2025-06-09T00:32:21.385Z
Learning: In the betaflight-configurator codebase, port paths use counter prefixes (e.g., "bluetooth1", "bluetooth2", "serial1") rather than direct protocol identifiers. The protocol selection logic correctly uses `portPath.startsWith("bluetooth")` to detect bluetooth ports regardless of the counter suffix, rather than direct string matching against protocol map keys.

Applied to files:

  • src/components/tabs/PortsTab.vue
📚 Learning: 2025-09-02T07:45:48.606Z
Learnt from: blckmn
Repo: betaflight/betaflight-configurator PR: 4583
File: src/js/tabs/firmware_flasher.js:949-961
Timestamp: 2025-09-02T07:45:48.606Z
Learning: In src/js/tabs/firmware_flasher.js, the config file loading code path after firmware loading (in the load_file click handler) cannot be reached when UF2 firmware is loaded, according to the maintainer blckmn. This code is maintained for backward compatibility with unified target settings and will be refactored in a future PR.

Applied to files:

  • src/components/tabs/PortsTab.vue
📚 Learning: 2025-08-27T22:07:26.353Z
Learnt from: haslinghuis
Repo: betaflight/betaflight-configurator PR: 4579
File: src/js/data_storage.js:9-9
Timestamp: 2025-08-27T22:07:26.353Z
Learning: In the betaflight-configurator CalVer refactoring, the generateVirtualApiVersions function in src/js/utils/common.js works correctly despite the apparent mathematical issue with bounds logic where compareVersions.minor() returns different ranges for API_VERSION_MAX_SUPPORTED (25.12.0) vs API_VERSION_ACCEPTED (1.44.0).

Applied to files:

  • src/components/tabs/PortsTab.vue
🧬 Code graph analysis (1)
src/js/vue_tab_mounter.js (3)
src/js/main.js (2)
  • tabName (139-139)
  • tabName (173-173)
src/js/vue_components.js (2)
  • VueTabComponents (11-14)
  • VueTabComponents (11-14)
src/js/gui.js (1)
  • GUI (473-473)
🪛 GitHub Check: SonarCloud Code Analysis
src/components/tabs/PortsTab.vue

[failure] 461-461: Expected { after 'if' condition.

See more on https://sonarcloud.io/project/issues?id=betaflight_betaflight-configurator&issues=AZrwvxt3oo37A_xrkyrD&open=AZrwvxt3oo37A_xrkyrD&pullRequest=4726


[failure] 464-464: Expected { after 'if' condition.

See more on https://sonarcloud.io/project/issues?id=betaflight_betaflight-configurator&issues=AZrwvxt3oo37A_xrkyrG&open=AZrwvxt3oo37A_xrkyrG&pullRequest=4726


[failure] 463-463: Expected { after 'if' condition.

See more on https://sonarcloud.io/project/issues?id=betaflight_betaflight-configurator&issues=AZrwvxt3oo37A_xrkyrF&open=AZrwvxt3oo37A_xrkyrF&pullRequest=4726


[failure] 462-462: Expected { after 'if' condition.

See more on https://sonarcloud.io/project/issues?id=betaflight_betaflight-configurator&issues=AZrwvxt3oo37A_xrkyrE&open=AZrwvxt3oo37A_xrkyrE&pullRequest=4726


[failure] 465-465: Expected { after 'if' condition.

See more on https://sonarcloud.io/project/issues?id=betaflight_betaflight-configurator&issues=AZrwvxt3oo37A_xrkyrH&open=AZrwvxt3oo37A_xrkyrH&pullRequest=4726

🔇 Additional comments (1)
src/js/vue_tab_mounter.js (1)

13-80: Vue tab mounting lifecycle looks solid.

Mount/unmount flow is clean: you guard against missing components/#content, unmount any previous app, clear the container, wire up i18n and betaflightModel, then reset GUI.tab_switch_in_progress and invoke the legacy contentReadyCallback. This should integrate well with the existing jQuery tab system without leaking Vue apps.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 60c6607 and 61c6991.

📒 Files selected for processing (1)
  • src/components/tabs/PortsTab.vue (1 hunks)
🧰 Additional context used
🧠 Learnings (8)
📓 Common learnings
Learnt from: haslinghuis
Repo: betaflight/betaflight-configurator PR: 4510
File: src/js/msp.js:384-391
Timestamp: 2025-09-19T20:42:20.332Z
Learning: Complex MSP duplicate handling fixes in Betaflight Configurator can cause infinite loading messages when changing tabs due to disruption of the callback resolution mechanism. Simple code-only duplicate detection (using this.callbacks.some((instance) => instance.code === code)) is the safer approach that preserves tab switching functionality.
📚 Learning: 2025-09-19T20:42:20.332Z
Learnt from: haslinghuis
Repo: betaflight/betaflight-configurator PR: 4510
File: src/js/msp.js:384-391
Timestamp: 2025-09-19T20:42:20.332Z
Learning: Complex MSP duplicate handling fixes in Betaflight Configurator can cause infinite loading messages when changing tabs due to disruption of the callback resolution mechanism. Simple code-only duplicate detection (using this.callbacks.some((instance) => instance.code === code)) is the safer approach that preserves tab switching functionality.

Applied to files:

  • src/components/tabs/PortsTab.vue
📚 Learning: 2025-09-19T20:41:44.286Z
Learnt from: haslinghuis
Repo: betaflight/betaflight-configurator PR: 4510
File: src/js/msp.js:384-391
Timestamp: 2025-09-19T20:41:44.286Z
Learning: When fixing MSP duplicate handling in Betaflight Configurator, avoid complex changes to callback resolution mechanisms as they can break tab switching functionality. Simple duplicate detection based on code and payload size is safer than complex requestKey-based approaches.

Applied to files:

  • src/components/tabs/PortsTab.vue
📚 Learning: 2025-06-20T12:35:49.283Z
Learnt from: mituritsyn
Repo: betaflight/betaflight-configurator PR: 4526
File: src/js/gui.js:43-43
Timestamp: 2025-06-20T12:35:49.283Z
Learning: In the Betaflight Configurator codebase, tabs in `defaultCloudBuildTabOptions` are conditionally displayed based on firmware build options. The logic in `serial_backend.js` checks `FC.CONFIG.buildOptions` and only adds tabs to `GUI.allowedTabs` if the firmware was built with support for that feature.

Applied to files:

  • src/components/tabs/PortsTab.vue
📚 Learning: 2025-06-27T22:06:49.210Z
Learnt from: haslinghuis
Repo: betaflight/betaflight-configurator PR: 4532
File: src/js/VirtualFC.js:234-234
Timestamp: 2025-06-27T22:06:49.210Z
Learning: In the betaflight-configurator codebase, the VirtualFC.js AUX_CONFIG array must stay synchronized with the betaflight firmware's msp_box.c definitions to ensure proper auxiliary mode functionality. Changes to mode names should follow the firmware source code even if it potentially breaks backward compatibility with saved presets.

Applied to files:

  • src/components/tabs/PortsTab.vue
📚 Learning: 2025-06-09T00:32:21.385Z
Learnt from: haslinghuis
Repo: betaflight/betaflight-configurator PR: 0
File: :0-0
Timestamp: 2025-06-09T00:32:21.385Z
Learning: In the betaflight-configurator codebase, port paths use counter prefixes (e.g., "bluetooth1", "bluetooth2", "serial1") rather than direct protocol identifiers. The protocol selection logic correctly uses `portPath.startsWith("bluetooth")` to detect bluetooth ports regardless of the counter suffix, rather than direct string matching against protocol map keys.

Applied to files:

  • src/components/tabs/PortsTab.vue
📚 Learning: 2025-09-02T07:45:48.606Z
Learnt from: blckmn
Repo: betaflight/betaflight-configurator PR: 4583
File: src/js/tabs/firmware_flasher.js:949-961
Timestamp: 2025-09-02T07:45:48.606Z
Learning: In src/js/tabs/firmware_flasher.js, the config file loading code path after firmware loading (in the load_file click handler) cannot be reached when UF2 firmware is loaded, according to the maintainer blckmn. This code is maintained for backward compatibility with unified target settings and will be refactored in a future PR.

Applied to files:

  • src/components/tabs/PortsTab.vue
📚 Learning: 2025-08-27T22:07:26.353Z
Learnt from: haslinghuis
Repo: betaflight/betaflight-configurator PR: 4579
File: src/js/data_storage.js:9-9
Timestamp: 2025-08-27T22:07:26.353Z
Learning: In the betaflight-configurator CalVer refactoring, the generateVirtualApiVersions function in src/js/utils/common.js works correctly despite the apparent mathematical issue with bounds logic where compareVersions.minor() returns different ranges for API_VERSION_MAX_SUPPORTED (25.12.0) vs API_VERSION_ACCEPTED (1.44.0).

Applied to files:

  • src/components/tabs/PortsTab.vue
🪛 GitHub Check: SonarCloud Code Analysis
src/components/tabs/PortsTab.vue

[failure] 463-463: Expected { after 'if' condition.

See more on https://sonarcloud.io/project/issues?id=betaflight_betaflight-configurator&issues=AZrwvxt3oo37A_xrkyrF&open=AZrwvxt3oo37A_xrkyrF&pullRequest=4726


[failure] 461-461: Expected { after 'if' condition.

See more on https://sonarcloud.io/project/issues?id=betaflight_betaflight-configurator&issues=AZrwvxt3oo37A_xrkyrD&open=AZrwvxt3oo37A_xrkyrD&pullRequest=4726


[failure] 462-462: Expected { after 'if' condition.

See more on https://sonarcloud.io/project/issues?id=betaflight_betaflight-configurator&issues=AZrwvxt3oo37A_xrkyrE&open=AZrwvxt3oo37A_xrkyrE&pullRequest=4726


[failure] 465-465: Expected { after 'if' condition.

See more on https://sonarcloud.io/project/issues?id=betaflight_betaflight-configurator&issues=AZrwvxt3oo37A_xrkyrH&open=AZrwvxt3oo37A_xrkyrH&pullRequest=4726


[failure] 464-464: Expected { after 'if' condition.

See more on https://sonarcloud.io/project/issues?id=betaflight_betaflight-configurator&issues=AZrwvxt3oo37A_xrkyrG&open=AZrwvxt3oo37A_xrkyrG&pullRequest=4726

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (3)
src/components/tabs/PortsTab.vue (3)

198-261: Function rules metadata (maxPorts/sharing) is currently unused for disabling options.

functionRules carries rich metadata (maxPorts, sharableWith, notSharableWith) but getRules/isRuleDisabled only use groups and dependsOn. That means we’re not enforcing port-count limits or cross-group exclusivity via disabled options (beyond the telemetry/peripheral mutual-exclusion handled in the change handlers), which may diverge from legacy ports.js behavior.

It would be good to either:

  • Port the old logic that uses maxPorts and the sharing constraints to compute disabled states, or
  • Explicitly confirm that the legacy tab also ignored those fields and that this metadata is intentionally unused.

Example of how this could be wired in without changing the public template API:

-        const isRuleDisabled = (rule) => {
-            return (
-                FC.CONFIG.buildOptions.length &&
-                rule.dependsOn !== undefined &&
-                !FC.CONFIG.buildOptions.includes(rule.dependsOn)
-            );
-        };
+        const isRuleDisabled = (rule) => {
+            // Build-option gating
+            if (FC.CONFIG.buildOptions.length && rule.dependsOn && !FC.CONFIG.buildOptions.includes(rule.dependsOn)) {
+                return true;
+            }
+
+            // Optional: enforce maxPorts / sharing based on current ports state
+            const allFunctions = FC.SERIAL_CONFIG?.ports?.flatMap((p) => p.functions || []) ?? [];
+            const usedCount = allFunctions.filter((f) => f === rule.name).length;
+            if (rule.maxPorts && usedCount >= rule.maxPorts) {
+                return true;
+            }
+
+            return false;
+        };

(You’d likely want a more faithful port of the legacy logic, including sharableWith/notSharableWith, but this illustrates the idea.)

If you’d like, I can sketch a closer port of the original functionRules availability logic from ports.js into a composable helper to keep setup() cleaner.

Also applies to: 323-335


364-381: Consider handling MSP_VTX_CONFIG failures so ports still load.

loadConfig always waits on MSP.promise(MSPCodes.MSP_VTX_CONFIG) before calling mspHelper.loadSerialConfig. If MSP_VTX_CONFIG isn’t supported or the promise rejects, handleSerialConfigLoaded is never reached, so ports never populate and GUI.content_ready() is never called, leaving the tab in a “loading” state.

To keep behavior robust on targets without VTX table support (or when the request fails), you could fall back to loading serial config even if MSP_VTX_CONFIG fails:

-        const loadConfig = () => {
-            MSP.promise(MSPCodes.MSP_VTX_CONFIG).then(() => {
-                mspHelper.loadSerialConfig(handleSerialConfigLoaded);
-            });
-        };
+        const loadConfig = () => {
+            MSP.promise(MSPCodes.MSP_VTX_CONFIG)
+                .catch(() => {
+                    // Ignore VTX config failure; ports tab should still work.
+                })
+                .finally(() => {
+                    mspHelper.loadSerialConfig(handleSerialConfigLoaded);
+                });
+        };

This keeps the MSP callback usage simple and avoids the infinite-loading style issues we’ve seen when MSP flows get too clever. Based on learnings, MSP callback handling is sensitive, so keeping this pattern straightforward is safer.


336-343: Verify SERIAL_CONFIG shape and AUTO baudrate mapping against legacy behavior.

The transform/save path currently:

  • Normalizes GPS/Blackbox baudrates to "AUTO" in component state and then maps "AUTO""57600" (GPS) and "115200" (Blackbox) on save.
  • Leaves telemetry "AUTO" as "AUTO" in FC.SERIAL_CONFIG.ports.
  • Rebuilds each port with only { identifier, msp_baudrate, telemetry_baudrate, gps_baudrate, blackbox_baudrate, functions }.

Two things worth double‑checking against the old ports.js and firmware expectations:

  1. AUTO representation:

    • Ensure that GPS/Blackbox AUTO really need to be serialized as "57600" / "115200" rather than a sentinel value (e.g., 0) and that telemetry AUTO should remain "AUTO" in FC.SERIAL_CONFIG.ports. Any mismatch here could cause unexpected baud changes on save.
  2. Port object shape:

    • If FC.SERIAL_CONFIG.ports contains additional fields (e.g., options/flags/inversion) today, rebuilding the array with a narrowed field set will drop them. In that case, we should carry them through from the original objects rather than omitting them.

For example, if ports include an options field, the reconstruction might need to look like:

-            FC.SERIAL_CONFIG.ports = ports.map((p) => {
+            FC.SERIAL_CONFIG.ports = ports.map((p, idx) => {
+                const original = FC.SERIAL_CONFIG.ports[idx] || {};
                 const functions = [];
                 // ...existing function pushes...
                 return {
-                    identifier: p.identifier,
-                    msp_baudrate: p.msp_baudrate,
-                    telemetry_baudrate: p.telemetry_baudrate,
-                    gps_baudrate: p.gps_baudrate === "AUTO" ? "57600" : p.gps_baudrate,
-                    blackbox_baudrate: p.blackbox_baudrate === "AUTO" ? "115200" : p.blackbox_baudrate,
-                    functions: functions,
+                    ...original,
+                    identifier: p.identifier,
+                    msp_baudrate: p.msp_baudrate,
+                    telemetry_baudrate: p.telemetry_baudrate,
+                    gps_baudrate: p.gps_baudrate === "AUTO" ? "57600" : p.gps_baudrate,
+                    blackbox_baudrate: p.blackbox_baudrate === "AUTO" ? "115200" : p.blackbox_baudrate,
+                    functions,
                 };
             });

If you can confirm the current FC.SERIAL_CONFIG.ports structure, I can help adjust transformPortData/saveConfig to be fully schema‑preserving while keeping the AUTO mapping intact.

Also applies to: 399-428, 450-497

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 61c6991 and 23f929b.

📒 Files selected for processing (1)
  • src/components/tabs/PortsTab.vue (1 hunks)
🧰 Additional context used
🧠 Learnings (9)
📓 Common learnings
Learnt from: haslinghuis
Repo: betaflight/betaflight-configurator PR: 4510
File: src/js/msp.js:384-391
Timestamp: 2025-09-19T20:42:20.332Z
Learning: Complex MSP duplicate handling fixes in Betaflight Configurator can cause infinite loading messages when changing tabs due to disruption of the callback resolution mechanism. Simple code-only duplicate detection (using this.callbacks.some((instance) => instance.code === code)) is the safer approach that preserves tab switching functionality.
Learnt from: haslinghuis
Repo: betaflight/betaflight-configurator PR: 4510
File: src/js/msp.js:384-391
Timestamp: 2025-09-19T20:41:44.286Z
Learning: When fixing MSP duplicate handling in Betaflight Configurator, avoid complex changes to callback resolution mechanisms as they can break tab switching functionality. Simple duplicate detection based on code and payload size is safer than complex requestKey-based approaches.
📚 Learning: 2025-06-20T12:35:49.283Z
Learnt from: mituritsyn
Repo: betaflight/betaflight-configurator PR: 4526
File: src/js/gui.js:43-43
Timestamp: 2025-06-20T12:35:49.283Z
Learning: In the Betaflight Configurator codebase, tabs in `defaultCloudBuildTabOptions` are conditionally displayed based on firmware build options. The logic in `serial_backend.js` checks `FC.CONFIG.buildOptions` and only adds tabs to `GUI.allowedTabs` if the firmware was built with support for that feature.

Applied to files:

  • src/components/tabs/PortsTab.vue
📚 Learning: 2025-09-19T20:42:20.332Z
Learnt from: haslinghuis
Repo: betaflight/betaflight-configurator PR: 4510
File: src/js/msp.js:384-391
Timestamp: 2025-09-19T20:42:20.332Z
Learning: Complex MSP duplicate handling fixes in Betaflight Configurator can cause infinite loading messages when changing tabs due to disruption of the callback resolution mechanism. Simple code-only duplicate detection (using this.callbacks.some((instance) => instance.code === code)) is the safer approach that preserves tab switching functionality.

Applied to files:

  • src/components/tabs/PortsTab.vue
📚 Learning: 2025-09-19T20:41:44.286Z
Learnt from: haslinghuis
Repo: betaflight/betaflight-configurator PR: 4510
File: src/js/msp.js:384-391
Timestamp: 2025-09-19T20:41:44.286Z
Learning: When fixing MSP duplicate handling in Betaflight Configurator, avoid complex changes to callback resolution mechanisms as they can break tab switching functionality. Simple duplicate detection based on code and payload size is safer than complex requestKey-based approaches.

Applied to files:

  • src/components/tabs/PortsTab.vue
📚 Learning: 2025-06-27T22:06:49.210Z
Learnt from: haslinghuis
Repo: betaflight/betaflight-configurator PR: 4532
File: src/js/VirtualFC.js:234-234
Timestamp: 2025-06-27T22:06:49.210Z
Learning: In the betaflight-configurator codebase, the VirtualFC.js AUX_CONFIG array must stay synchronized with the betaflight firmware's msp_box.c definitions to ensure proper auxiliary mode functionality. Changes to mode names should follow the firmware source code even if it potentially breaks backward compatibility with saved presets.

Applied to files:

  • src/components/tabs/PortsTab.vue
📚 Learning: 2025-06-09T00:32:21.385Z
Learnt from: haslinghuis
Repo: betaflight/betaflight-configurator PR: 0
File: :0-0
Timestamp: 2025-06-09T00:32:21.385Z
Learning: In the betaflight-configurator codebase, port paths use counter prefixes (e.g., "bluetooth1", "bluetooth2", "serial1") rather than direct protocol identifiers. The protocol selection logic correctly uses `portPath.startsWith("bluetooth")` to detect bluetooth ports regardless of the counter suffix, rather than direct string matching against protocol map keys.

Applied to files:

  • src/components/tabs/PortsTab.vue
📚 Learning: 2025-09-02T07:45:48.606Z
Learnt from: blckmn
Repo: betaflight/betaflight-configurator PR: 4583
File: src/js/tabs/firmware_flasher.js:949-961
Timestamp: 2025-09-02T07:45:48.606Z
Learning: In src/js/tabs/firmware_flasher.js, the config file loading code path after firmware loading (in the load_file click handler) cannot be reached when UF2 firmware is loaded, according to the maintainer blckmn. This code is maintained for backward compatibility with unified target settings and will be refactored in a future PR.

Applied to files:

  • src/components/tabs/PortsTab.vue
📚 Learning: 2025-08-27T22:07:26.353Z
Learnt from: haslinghuis
Repo: betaflight/betaflight-configurator PR: 4579
File: src/js/data_storage.js:9-9
Timestamp: 2025-08-27T22:07:26.353Z
Learning: In the betaflight-configurator CalVer refactoring, the generateVirtualApiVersions function in src/js/utils/common.js works correctly despite the apparent mathematical issue with bounds logic where compareVersions.minor() returns different ranges for API_VERSION_MAX_SUPPORTED (25.12.0) vs API_VERSION_ACCEPTED (1.44.0).

Applied to files:

  • src/components/tabs/PortsTab.vue
📚 Learning: 2025-11-28T22:51:11.691Z
Learnt from: haslinghuis
Repo: betaflight/betaflight-configurator PR: 4714
File: src/js/protocols/CapacitorBluetooth.js:176-206
Timestamp: 2025-11-28T22:51:11.691Z
Learning: In betaflight-configurator, Web Bluetooth API's `services` option filters scan results to only devices advertising those UUIDs, while `optionalServices` requests permission to access services after connection without filtering. PWA (WebBluetooth.js) uses `{ acceptAllDevices: true, optionalServices: uuids }` to show all devices while requesting post-connection service access. CapacitorBluetooth.js should match this pattern rather than using `services` as a scan filter.

Applied to files:

  • src/components/tabs/PortsTab.vue

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
src/components/tabs/PortsTab.vue (1)

340-357: Consider guarding MSP_VTX_CONFIG so serial config still loads when VTX tables are unsupported

loadConfig currently does:

const loadConfig = () => {
    MSP.promise(MSPCodes.MSP_VTX_CONFIG).then(() => {
        mspHelper.loadSerialConfig(handleSerialConfigLoaded);
    });
};

If the FC firmware does not implement MSP_VTX_CONFIG, this promise will reject and loadSerialConfig (and thus handleSerialConfigLoaded / GUI.content_ready()) will never run, leaving the Ports table empty for those targets.

If we still support any API versions without MSP_VTX_CONFIG, please consider a .catch / .finally pattern so serial config always loads, e.g.:

- const loadConfig = () => {
-     MSP.promise(MSPCodes.MSP_VTX_CONFIG).then(() => {
-         mspHelper.loadSerialConfig(handleSerialConfigLoaded);
-     });
- };
+ const loadConfig = () => {
+     MSP.promise(MSPCodes.MSP_VTX_CONFIG)
+         .catch(() => {
+             // Older firmware or VTX tables not supported – ignore and proceed.
+         })
+         .finally(() => {
+             mspHelper.loadSerialConfig(handleSerialConfigLoaded);
+         });
+ };

If configurator’s supported API range guarantees MSP_VTX_CONFIG is always present, this is less critical but still avoids unhandled rejections.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 23f929b and dcc5237.

📒 Files selected for processing (7)
  • src/components/tabs/PortsTab.vue (1 hunks)
  • src/js/main.js (1 hunks)
  • src/js/protocols/CapacitorSerial.js (5 hunks)
  • src/js/tabs/ports.js (0 hunks)
  • src/js/vue_components.js (2 hunks)
  • src/js/vue_tab_mounter.js (1 hunks)
  • src/tabs/ports.html (0 hunks)
💤 Files with no reviewable changes (2)
  • src/js/tabs/ports.js
  • src/tabs/ports.html
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/js/main.js
🧰 Additional context used
🧠 Learnings (19)
📓 Common learnings
Learnt from: haslinghuis
Repo: betaflight/betaflight-configurator PR: 4510
File: src/js/msp.js:384-391
Timestamp: 2025-09-19T20:42:20.332Z
Learning: Complex MSP duplicate handling fixes in Betaflight Configurator can cause infinite loading messages when changing tabs due to disruption of the callback resolution mechanism. Simple code-only duplicate detection (using this.callbacks.some((instance) => instance.code === code)) is the safer approach that preserves tab switching functionality.
📚 Learning: 2025-09-10T18:26:10.136Z
Learnt from: haslinghuis
Repo: betaflight/betaflight-configurator PR: 4601
File: package.json:125-125
Timestamp: 2025-09-10T18:26:10.136Z
Learning: In betaflight-configurator, dependency updates are handled incrementally - Vue dependencies are updated separately from Vite dependencies for better isolation and maintainability.

Applied to files:

  • src/js/vue_components.js
  • src/js/vue_tab_mounter.js
📚 Learning: 2025-06-20T12:35:49.283Z
Learnt from: mituritsyn
Repo: betaflight/betaflight-configurator PR: 4526
File: src/js/gui.js:43-43
Timestamp: 2025-06-20T12:35:49.283Z
Learning: In the Betaflight Configurator codebase, tabs in `defaultCloudBuildTabOptions` are conditionally displayed based on firmware build options. The logic in `serial_backend.js` checks `FC.CONFIG.buildOptions` and only adds tabs to `GUI.allowedTabs` if the firmware was built with support for that feature.

Applied to files:

  • src/components/tabs/PortsTab.vue
📚 Learning: 2025-09-19T20:42:20.332Z
Learnt from: haslinghuis
Repo: betaflight/betaflight-configurator PR: 4510
File: src/js/msp.js:384-391
Timestamp: 2025-09-19T20:42:20.332Z
Learning: Complex MSP duplicate handling fixes in Betaflight Configurator can cause infinite loading messages when changing tabs due to disruption of the callback resolution mechanism. Simple code-only duplicate detection (using this.callbacks.some((instance) => instance.code === code)) is the safer approach that preserves tab switching functionality.

Applied to files:

  • src/components/tabs/PortsTab.vue
  • src/js/vue_tab_mounter.js
📚 Learning: 2025-09-19T20:41:44.286Z
Learnt from: haslinghuis
Repo: betaflight/betaflight-configurator PR: 4510
File: src/js/msp.js:384-391
Timestamp: 2025-09-19T20:41:44.286Z
Learning: When fixing MSP duplicate handling in Betaflight Configurator, avoid complex changes to callback resolution mechanisms as they can break tab switching functionality. Simple duplicate detection based on code and payload size is safer than complex requestKey-based approaches.

Applied to files:

  • src/components/tabs/PortsTab.vue
  • src/js/vue_tab_mounter.js
📚 Learning: 2025-06-27T22:06:49.210Z
Learnt from: haslinghuis
Repo: betaflight/betaflight-configurator PR: 4532
File: src/js/VirtualFC.js:234-234
Timestamp: 2025-06-27T22:06:49.210Z
Learning: In the betaflight-configurator codebase, the VirtualFC.js AUX_CONFIG array must stay synchronized with the betaflight firmware's msp_box.c definitions to ensure proper auxiliary mode functionality. Changes to mode names should follow the firmware source code even if it potentially breaks backward compatibility with saved presets.

Applied to files:

  • src/components/tabs/PortsTab.vue
📚 Learning: 2025-06-09T00:32:21.385Z
Learnt from: haslinghuis
Repo: betaflight/betaflight-configurator PR: 0
File: :0-0
Timestamp: 2025-06-09T00:32:21.385Z
Learning: In the betaflight-configurator codebase, port paths use counter prefixes (e.g., "bluetooth1", "bluetooth2", "serial1") rather than direct protocol identifiers. The protocol selection logic correctly uses `portPath.startsWith("bluetooth")` to detect bluetooth ports regardless of the counter suffix, rather than direct string matching against protocol map keys.

Applied to files:

  • src/components/tabs/PortsTab.vue
📚 Learning: 2025-09-02T07:45:48.606Z
Learnt from: blckmn
Repo: betaflight/betaflight-configurator PR: 4583
File: src/js/tabs/firmware_flasher.js:949-961
Timestamp: 2025-09-02T07:45:48.606Z
Learning: In src/js/tabs/firmware_flasher.js, the config file loading code path after firmware loading (in the load_file click handler) cannot be reached when UF2 firmware is loaded, according to the maintainer blckmn. This code is maintained for backward compatibility with unified target settings and will be refactored in a future PR.

Applied to files:

  • src/components/tabs/PortsTab.vue
📚 Learning: 2025-08-27T22:07:26.353Z
Learnt from: haslinghuis
Repo: betaflight/betaflight-configurator PR: 4579
File: src/js/data_storage.js:9-9
Timestamp: 2025-08-27T22:07:26.353Z
Learning: In the betaflight-configurator CalVer refactoring, the generateVirtualApiVersions function in src/js/utils/common.js works correctly despite the apparent mathematical issue with bounds logic where compareVersions.minor() returns different ranges for API_VERSION_MAX_SUPPORTED (25.12.0) vs API_VERSION_ACCEPTED (1.44.0).

Applied to files:

  • src/components/tabs/PortsTab.vue
📚 Learning: 2025-11-28T22:51:11.691Z
Learnt from: haslinghuis
Repo: betaflight/betaflight-configurator PR: 4714
File: src/js/protocols/CapacitorBluetooth.js:176-206
Timestamp: 2025-11-28T22:51:11.691Z
Learning: In betaflight-configurator, Web Bluetooth API's `services` option filters scan results to only devices advertising those UUIDs, while `optionalServices` requests permission to access services after connection without filtering. PWA (WebBluetooth.js) uses `{ acceptAllDevices: true, optionalServices: uuids }` to show all devices while requesting post-connection service access. CapacitorBluetooth.js should match this pattern rather than using `services` as a scan filter.

Applied to files:

  • src/components/tabs/PortsTab.vue
  • src/js/protocols/CapacitorSerial.js
📚 Learning: 2025-12-01T17:09:13.412Z
Learnt from: haslinghuis
Repo: betaflight/betaflight-configurator PR: 0
File: :0-0
Timestamp: 2025-12-01T17:09:13.412Z
Learning: In betaflight-configurator’s CapacitorBluetooth.js, native plugin listeners (notification, services, connectionState) must be attached exactly once per app lifetime. Re-attaching them during writes/connect cycles leads to duplicate handlers, increased JS workload, MSP queue pressure, and can prevent the MSP_EEPROM_WRITE callback from firing (so reinitializeConnection is not reached). Implement an idempotent _attachNativeListenersOnce() guard.

Applied to files:

  • src/js/protocols/CapacitorSerial.js
📚 Learning: 2025-11-24T15:07:25.227Z
Learnt from: haslinghuis
Repo: betaflight/betaflight-configurator PR: 4706
File: src/js/protocols/CapacitorSerial.js:47-47
Timestamp: 2025-11-24T15:07:25.227Z
Learning: In betaflight-configurator's CapacitorSerial protocol (src/js/protocols/CapacitorSerial.js), the fire-and-forget this.loadDevices() call in the constructor is intentional background pre-loading. The actual critical device list loading for UI population is orchestrated by port_handler.js via serial.getDevices("serial"), which properly awaits CapacitorSerial.getDevices() → loadDevices() in a complete async chain. The constructor's async call does not cause race conditions.

Applied to files:

  • src/js/protocols/CapacitorSerial.js
📚 Learning: 2025-10-25T21:16:32.474Z
Learnt from: haslinghuis
Repo: betaflight/betaflight-configurator PR: 4379
File: src/js/protocols/TauriSerial.js:203-259
Timestamp: 2025-10-25T21:16:32.474Z
Learning: In TauriSerial (src/js/protocols/TauriSerial.js), the requestPermissionDevice() method is not needed and not invoked. Tauri automatically discovers serial devices through the constructor's loadDevices() and startDeviceMonitoring() calls, bypassing the browser permission model that WebSerial requires. Devices are auto-detected via a 1-second polling interval without user permission prompts.

Applied to files:

  • src/js/protocols/CapacitorSerial.js
📚 Learning: 2025-11-28T22:41:59.374Z
Learnt from: haslinghuis
Repo: betaflight/betaflight-configurator PR: 4714
File: src/js/protocols/CapacitorBluetooth.js:135-143
Timestamp: 2025-11-28T22:41:59.374Z
Learning: In betaflight-configurator, WebSerial.js uses a constant path "serial" for all devices, and CapacitorBluetooth.js uses constant path "bluetooth", consistent with the single-device-at-a-time workflow. WebBluetooth.js uses counter-based paths (bluetooth_${counter}), but that's specific to its implementation. The path strategy varies by protocol based on their specific requirements.

Applied to files:

  • src/js/protocols/CapacitorSerial.js
📚 Learning: 2025-06-19T22:13:09.136Z
Learnt from: blckmn
Repo: betaflight/betaflight-configurator PR: 4521
File: src/js/protocols/WebSerial.js:148-151
Timestamp: 2025-06-19T22:13:09.136Z
Learning: In WebSerial.js, there's a timing issue where the cached `this.ports` array doesn't immediately reflect newly permitted devices after `requestPermissionDevice()` completes. The `getDevices()` method needs to refresh the device list from the browser API to return accurate data immediately following a permission request and user acceptance.

Applied to files:

  • src/js/protocols/CapacitorSerial.js
📚 Learning: 2025-08-22T16:43:20.901Z
Learnt from: haslinghuis
Repo: betaflight/betaflight-configurator PR: 4576
File: src/js/port_usage.js:17-23
Timestamp: 2025-08-22T16:43:20.901Z
Learning: In betaflight-configurator, the serial facade architecture requires accessing metrics like bitrate, bytesReceived, and bytesSent from serial._protocol rather than the top-level serial object. This change maintains compatibility with existing port utilization calculations that have been stable for over 11 years.

Applied to files:

  • src/js/protocols/CapacitorSerial.js
📚 Learning: 2025-11-22T21:18:08.814Z
Learnt from: haslinghuis
Repo: betaflight/betaflight-configurator PR: 4706
File: src/js/protocols/CapacitorSerial.js:11-46
Timestamp: 2025-11-22T21:18:08.814Z
Learning: In betaflight-configurator, protocol implementations (WebSerial, CapacitorSerial, WebBluetooth, etc.) are instantiated once in the Serial class constructor and stored as singletons in the _protocols array for the entire application lifetime. They are never destroyed or recreated, so cleanup methods to remove event listeners are not needed.

Applied to files:

  • src/js/protocols/CapacitorSerial.js
📚 Learning: 2025-12-02T17:46:47.859Z
Learnt from: haslinghuis
Repo: betaflight/betaflight-configurator PR: 0
File: :0-0
Timestamp: 2025-12-02T17:46:47.859Z
Learning: BetaflightBluetoothPlugin.java: maintainers prefer write(PluginCall) to be “write-only” — no per-call UUID parsing or characteristic lookups. Resolve and cache the write characteristic once in onServicesDiscovered() (prefer PROPERTY_WRITE_NO_RESPONSE; else PROPERTY_WRITE). write() should read payload params from call, use the cached writeCharacteristic, pick write type, and submitWrite; clear the cache in cleanupGatt().

Applied to files:

  • src/js/protocols/CapacitorSerial.js
📚 Learning: 2025-08-27T19:36:13.733Z
Learnt from: haslinghuis
Repo: betaflight/betaflight-configurator PR: 4579
File: src/js/tabs/pid_tuning.js:80-82
Timestamp: 2025-08-27T19:36:13.733Z
Learning: MSP_STATUS_EX calls in serial_backend.js and setup.js are intentionally unguarded because they are used for status fetching on page load to initialize profiles and are safe to call on older firmware versions. Only conditional/optional MSP_STATUS_EX usage (like in pid_tuning.js) needs version gating to API_VERSION_25_12.

Applied to files:

  • src/js/protocols/CapacitorSerial.js
🧬 Code graph analysis (2)
src/js/vue_components.js (2)
src/components/init.js (1)
  • app (60-64)
src/js/vue_loader.js (1)
  • app (42-42)
src/js/vue_tab_mounter.js (2)
src/js/main.js (2)
  • tabName (139-139)
  • tabName (173-173)
src/js/gui.js (1)
  • GUI (473-473)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
🔇 Additional comments (4)
src/js/vue_components.js (1)

10-18: PortsTab Vue integration and registration look consistent

The new PortsTab import, VueTabComponents.ports entry, and global "PortsTab" registration align with the existing tab pattern and the mountVueTab("ports", ...) usage. No issues from a wiring/registration perspective.

Also applies to: 30-34

src/js/vue_tab_mounter.js (1)

62-64: Tab mount lifecycle and betaflightModel injection changes look correct

  • Switching from window.vm to globalThis.vm still accesses the same global app instance in browsers while being more robust.
  • Always scheduling the zero‑delay timeout to clear GUI.tab_switch_in_progress (and conditionally call contentReadyCallback inside it) makes tab switching more reliable when callers omit the callback.

This stays within the existing lifecycle model and improves robustness.

Also applies to: 71-77

src/components/tabs/PortsTab.vue (1)

363-404: Ports save path, AUTO baud mapping, and feature auto-enabling look faithful to legacy behavior

The saveConfig + getEnabledFeaturesFromPorts + updateFeatures pipeline appears consistent and well-structured:

  • Rebuilds FC.SERIAL_CONFIG.ports from the reactive ports model, with clear mapping from booleans / selected rules back to the functions array.
  • Maps "AUTO" GPS and Blackbox baudrates to concrete defaults ("57600" and "115200" respectively) when persisting, while keeping "AUTO" in component state, which matches the described AUTO‑baud behavior.
  • Derives rxSerial, telemetry, blackbox, esc, and gps flags from the persisted functions and updates FC.FEATURE_CONFIG.features accordingly, preserving the historical behavior of only enabling TELEMETRY without auto‑disabling it when telemetry is no longer present.
  • Uses vtxTableNotConfigured to show the VTX‑table warning only when the MSP‑provided table is marked available but has zero bands/channels/powerlevels.

Overall this path looks solid and aligns with the existing configurator feature‑autoconfig semantics.

Also applies to: 426-473, 475-483

src/js/protocols/CapacitorSerial.js (1)

12-32: State initialization must occur before plugin availability check to ensure stable defaults

The constructor currently performs an early return if (!BetaflightSerial) before initializing instance properties like this.ports, this.connected, this.bitrate, etc. If the plugin is unavailable, these properties remain undefined instead of safe defaults, which can cause downstream errors when code expects them to exist.

Initialize all state unconditionally at the start of the constructor, then check availability afterward:

 class CapacitorSerial extends EventTarget {
     constructor() {
         super();
-
-        if (!BetaflightSerial) {
-            console.error(`${logHead} Native BetaflightSerial plugin is not available`);
-            return;
-        }
-
         this.connected = false;
         this.openRequested = false;
         this.transmitting = false;
         this.connectionInfo = null;
 
         this.bitrate = 0;
         this.bytesSent = 0;
         this.bytesReceived = 0;
 
         this.ports = [];
         this.currentDevice = null;
         this.connectionId = null;
+
+        if (!BetaflightSerial) {
+            console.error(`${logHead} Native BetaflightSerial plugin is not available`);
+            return;
+        }

This ensures the instance is always in a valid state regardless of plugin availability.

Copy link
Member

@haslinghuis haslinghuis left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please remove Capacitor part

Copy link
Member

@haslinghuis haslinghuis left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry was referring to removing changes from the PR. Otherwise tested okay.

These files are needed for Android:

  • restore src/js/protocols/CapacitorSerial.js‎
  • remove changes from src/serial.js

@sonarqubecloud
Copy link

sonarqubecloud bot commented Dec 8, 2025

Quality Gate Failed Quality Gate failed

Failed conditions
6.5% Duplication on New Code (required ≤ 3%)

See analysis details on SonarQube Cloud

@github-actions
Copy link
Contributor

github-actions bot commented Dec 8, 2025

Copy link
Member

@nerdCopter nerdCopter left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • approving, briefly tested ports tab.

@haslinghuis haslinghuis merged commit 7c71005 into betaflight:master Dec 9, 2025
7 of 8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants