Skip to content

feat: Migrate PID tuning tab to Vue 3 / Pinia#4848

Open
haslinghuis wants to merge 72 commits intobetaflight:masterfrom
haslinghuis:vue-pid-tuning
Open

feat: Migrate PID tuning tab to Vue 3 / Pinia#4848
haslinghuis wants to merge 72 commits intobetaflight:masterfrom
haslinghuis:vue-pid-tuning

Conversation

@haslinghuis
Copy link
Member

@haslinghuis haslinghuis commented Feb 3, 2026

1. Architectural Shift: From Monolith to Sub-Tabs

The legacy pid_tuning.js was a "mega-file" that handled everything from rate calculations to filter graphs. PR #4848 breaks this down into a more maintainable Vue structure:

Component-Based Sub-tabs: It separates the PID Settings, Filter Settings, and Rate Settings into distinct Vue components. This mirrors the pattern seen in the Receiver Tab migration but at a much larger scale.

The Store Pattern: Like other recent migrations (e.g., the Setup Tab), it heavily utilizes a centralized store (likely Pinia) to sync the MSP_PID, MSP_FILTER_CONFIG, and MSP_RC_TUNING data. This ensures that if you change a value in the "Simplified Tuning" slider, the raw PID values in the adjacent sub-tab stay in sync without manual DOM refreshes.

2. The "Simplified Tuning" Logic

One of the biggest challenges in this PR is the Simplified Tuning Sliders.

Legacy: In the jQuery version, moving a slider triggered a chain of imperative math functions that recalculated hidden fields before sending the MSP command.

Vue Migration: PR #4848 implements this using Computed Properties. This is a significant upgrade in reliability; the relationship between the "Master Multiplier" and the individual Kp​,Ki​,Kd​ values is now declarative. It’s much harder for the UI to show a value that doesn't match what the Flight Controller (FC) is actually receiving.

3. Rate Preview & Graphing

The Rates sub-tab migration is the "heavy lifter" of this PR.

Current implementation: Uses a custom canvas-based drawing routine.

Migration: The PR refactors this into a reactive Vue component. It has to handle multiple "Rate Types" (Betaflight, Actual, RaceFlight, etc.).

Consistency: It follows the visual style of the Blackbox Explorer Vue components, aiming for a unified "look and feel" for all graph data across the Betaflight ecosystem.

4. Safety & Validation

The PID tab is where users are most likely to "brick" their flight performance with a typo.

Input Validation: PR #4848 leverages Vue’s reactive form validation. Unlike the legacy version, which often relied on onBlur events to catch errors, the Vue version can provide real-time feedback (red borders/tooltips) as the user types, preventing invalid MSP packets from being sent.

Feature Legacy (jQuery/JS) Vue Migration (PR #4848)
State Management Global variables, manual DOM updates Centralized Store (Reactive)
Sub-Tab Logic Hidden/Shown divs via jQuery Vue Router or Component Switching
Validation Post-input (often inconsistent) Real-time reactive validation
Simplified Tuning Imperative math triggers Declarative Computed Properties
Code Size One massive, brittle file Modular, testable components

Consistency with other Migrations

This PR follows the "Gold Standard" set by the Receiver Tab (#4830) migration by:

  • Using Composition API for better logic reuse.
  • Standardizing on the new CSS variable-based theming, which allows for better dark/light mode support.
  • Implementing Profile Switching as a top-level state change that triggers a re-fetch of all sub-component data.

Summary by CodeRabbit

  • New Features

    • Reimplemented PID Tuning tab with PID, Rates, and Filter sub-tabs: live tuning sliders, expert-mode controls, Show All PIDs, save/revert with change tracking, profile and rate-profile management (select, edit, copy, reset), visual previews (rate/throttle curves and 3D rates), and cross-tab expert-mode sync.
    • Global profile selection dialog; dialogs wired into the UI.
  • Localization

    • Added translation for "PID Controller Advanced Settings".
  • Documentation

    • Added comprehensive migration and rollout planning document.

@haslinghuis haslinghuis added this to the 2026.6 milestone Feb 3, 2026
@haslinghuis haslinghuis self-assigned this Feb 3, 2026
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 3, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Adds a complete Vue 3 + Pinia implementation of the PID Tuning tab: new PidTuningTab with PID/Rates/Filter sub-tabs, Pinia pidTuning store for snapshot/change tracking, profile selection dialog + composable, tab mounting/adapter wiring, i18n key, and a detailed MIGRATION.md plan.

Changes

Cohort / File(s) Summary
Top-level tab
src/components/tabs/PidTuningTab.vue
New Vue SFC: top-level PID Tuning tab with profile/rate selectors, sub-tab navigation (PID/Rates/Filter), coordinated MSP/FC load sequence (API gated), save/revert/copy/reset workflows, hasChanges tracking and slider reinitialization.
Sub-tabs
src/components/tabs/pid-tuning/PidSubTab.vue, src/components/tabs/pid-tuning/RatesSubTab.vue, src/components/tabs/pid-tuning/FilterSubTab.vue
Three new Vue SFCs: PidSubTab (extensive sliders, expert gating, exposes forceUpdateSliders), RatesSubTab (rate profile UI, canvases for rate/throttle/3D previews), FilterSubTab (global & per-filter controls, restore-on-enable logic, MSP calc triggers).
Store & State bridge
src/stores/pidTuning.js, src/js/tab_state.js
New Pinia store usePidTuningStore for deep snapshots, change detection, and revert; tabState reactive bridge exposing expertMode to link legacy tab adapters and Vue.
Dialogs & composable
src/components/dialogs/ProfileSelectionDialog.vue, src/components/dialogs/GlobalDialogs.vue, src/composables/useDialog.js
ProfileSelectionDialog component added and registered in GlobalDialogs; composable openProfileSelection(...) added to open dialog and handle confirm/cancel flows.
Vue integration / mounting
src/js/vue_components.js, src/js/vue_tab_mounter.js, src/js/main.js, src/components/tabs/SetupTab.vue
Register PidTuningTab in VueTabComponents and global app; switch pid_tuning to mountVueTab; mount flow wires a tab adapter into legacy TABS with cleanup and expertModeChanged bridging; SetupTab exposes cleanup via defineExpose.
Localization & docs
locales/en/messages.json, MIGRATION.md
Adds i18n key pidTuningPidControllerAdvancedSettings and a comprehensive MIGRATION.md with migration plan, phased implementation, testing, rollback, and file inventories.
Dialogs registry update
src/components/dialogs/GlobalDialogs.vue
Registers new ProfileSelectionDialog in the dialogs mapping.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant PidTuningTab
    participant PiniaStore
    participant FC
    participant MSP

    User->>PidTuningTab: Mount component
    PidTuningTab->>FC: Read CONFIG (profiles)
    PidTuningTab->>MSP: Request controller/profile names (API gated)
    PidTuningTab->>MSP: Request PID, advanced, RC tuning, filter config
    MSP-->>PidTuningTab: Return data
    PidTuningTab->>PiniaStore: storeOriginals(...)
    User->>PidTuningTab: Modify sliders
    PidTuningTab->>PiniaStore: checkForChanges(...)
    PiniaStore-->>PidTuningTab: hasChanges = true
    User->>PidTuningTab: Click Save
    PidTuningTab->>MSP: MSP2_SET_TEXT (profile names) (API gated)
    PidTuningTab->>MSP: Persist (EEPROM/write)
    PidTuningTab->>FC: Update CONFIG
    PidTuningTab->>PiniaStore: storeOriginals(...) (reset)
    PidTuningTab->>PidTuningTab: Reinitialize sliders
    User->>PidTuningTab: Click Revert
    PidTuningTab->>PiniaStore: revertToOriginals()
    PiniaStore->>FC: Restore FC data
    PidTuningTab->>PidTuningTab: Reinitialize sliders
Loading
sequenceDiagram
    participant User
    participant PidTuningTab
    participant useDialog
    participant ProfileSelectionDialog
    participant MSP
    participant FC

    User->>PidTuningTab: Click "Copy Profile"
    PidTuningTab->>useDialog: openProfileSelection(...)
    useDialog->>ProfileSelectionDialog: show()
    ProfileSelectionDialog-->>User: display options
    User->>ProfileSelectionDialog: Select & confirm
    ProfileSelectionDialog-->>useDialog: emit confirm(selected)
    useDialog-->>PidTuningTab: onConfirm(selected)
    PidTuningTab->>FC: Prepare COPY_PROFILE payload
    PidTuningTab->>MSP: MSP_COPY_PROFILE
    MSP-->>FC: Profile copied
    PidTuningTab->>PidTuningTab: Reload MSP/FC data
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

Suggested reviewers

  • nerdCopter
🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Description check ❓ Inconclusive The PR description comprehensively covers the migration scope, including architectural shifts, simplified tuning logic, rate preview implementation, safety/validation improvements, and consistency with other migrations, but does not follow the repository's required PR template structure. Follow the repository's pull request template structure provided in the description_template. The description should be reorganized to address the template sections and requirements, such as ensuring the pull request is against master, references any related issues, and includes a final cleanup by removing the template instructions.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The PR title 'feat: Migrate PID tuning tab to Vue 3 / Pinia' accurately summarizes the primary architectural change—migrating legacy jQuery code to a Vue 3 and Pinia-based implementation.
Docstring Coverage ✅ Passed Docstring coverage is 85.71% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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.

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: 2

🤖 Fix all issues with AI agents
In `@src/components/tabs/pid-tuning/PidSubTab.vue`:
- Around line 521-545: Remove the temporary debug console.log calls inside
initializeSliders (the logs that reference TuningSliders.sliderDGain,
sliderDGain.value, and sliderDGainDisplay.value) and any similar console.log
statements around lines 608–611; keep the assignment logic (sliderPidsMode,
sliderDGain, sliderPIGain, etc.), nextTick() call, and profileName handling
intact so functionality is unchanged, only stripping out the debug output.

In `@src/js/main.js`:
- Around line 516-522: Remove the verbose debug console.log call before merging:
delete the console.log(...) statement that logs GUI.active_tab and TABS around
the expertModeChanged invocation (the block referencing GUI.active_tab and
TABS[GUI.active_tab]), leaving only the call to
TABS[GUI.active_tab]?.expertModeChanged?.(checked); or alternatively gate the
log behind a debug flag if runtime diagnostics are required.
🧹 Nitpick comments (2)
MIGRATION.md (1)

487-487: Optional: Add language specifiers to fenced code blocks.

Static analysis flagged several fenced code blocks without language specifiers (lines 487, 671, 737, 874, etc.). Adding language identifiers (e.g., ```text or ```plaintext) would improve markdown compliance.

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

360-364: Consider removing unused cleanup function.

The cleanup function is defined but never exposed or called. The tabAdapter in vue_tab_mounter.js provides its own cleanup implementation that simply invokes the callback immediately.

🧹 Proposed removal
-// Cleanup callback - called from gui.js tab_switch_cleanup when switching away from this tab
-function cleanup(callback) {
-    // Any cleanup needed before unmounting
-    // Call the callback to signal cleanup is complete
-    if (callback) callback();
-}
-
 // No need to expose methods - using global reactive state via tabState
 // defineExpose is not needed anymore

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: 3

🤖 Fix all issues with AI agents
In `@src/components/tabs/pid-tuning/FilterSubTab.vue`:
- Around line 501-508: The number input bound to dterm_lowpass_dyn_max_hz is
unusable because both min and max are set to 1000; update the input attributes
in the component (the <input> controlled by dterm_lowpass_dyn_max_hz shown next
to dtermLowpassEnabled and dtermLowpassMode) so the max is larger than the min
(or remove the max constraint) and choose a sensible upper bound (e.g., 10000 or
the system’s allowed maximum) and adjust step/min as needed to allow user
adjustments.

In `@src/components/tabs/pid-tuning/PidSubTab.vue`:
- Around line 1294-1315: The onSliderChange handler is scheduling multiple
setTimeouts which can flip isUserInteracting.value too early; introduce a
component-scoped timer id (e.g., userInteractionTimeout or
userInteractionTimeoutId) and call clearTimeout(userInteractionTimeout) at the
start of onSliderChange before creating the new timeout, then assign the result
of setTimeout to that variable; ensure the timer variable is declared outside
onSliderChange (and cleared in the component unmount/cleanup) so rapid slider
events cancel previous timers and only the latest timeout resets
isUserInteracting.value.

In `@src/components/tabs/pid-tuning/RatesSubTab.vue`:
- Around line 1527-1531: The throttle curve watcher only tracks throttleMid,
throttleHover and throttleExpo so drawThrottleCurve() can miss updates when
throttleLimitType or throttleLimitPercent change; update the watcher to include
throttleLimitType and throttleLimitPercent (alongside
throttleMid/throttleHover/throttleExpo) so that nextTick -> drawThrottleCurve()
runs whenever any of those reactive values change, referencing the existing
watcher and drawThrottleCurve() function to make the change.

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: 2

🤖 Fix all issues with AI agents
In `@src/components/tabs/pid-tuning/PidSubTab.vue`:
- Around line 1063-1088: The profile name edits stored in the profileName ref
are never written back to the legacy config array; update the save/submit
handler that persists PID settings to set
FC.CONFIG.pidProfileNames[FC.CONFIG.profile] = profileName.value.trim() (and
then trigger whatever existing persistence/refresh logic runs after saving).
Locate the profileName ref and the save method used for PID profiles and ensure
you write the trimmed profileName.value into FC.CONFIG.pidProfileNames at index
FC.CONFIG.profile before completing the save.
- Around line 876-915: The TPA inputs are bound to the wrong RC_TUNING fields:
change the tpaBreakpoint input to bind to rcTuning.dynamic_THR_breakpoint
(replace v-model.number="rcTuning.TPA_BREAKPOINT"), remove or stop binding the
tpaRate input to dynamic_THR_breakpoint
(v-model.number="rcTuning.dynamic_THR_breakpoint") and instead either
remove/conditionally render the rate input when the rate field doesn't exist
(API < 1.45) or bind it only when a proper rate property exists; update
references to rcTuning.dynamic_THR_PID, rcTuning.dynamic_THR_breakpoint, and the
tpaRate/tpaBreakpoint inputs in PidSubTab.vue accordingly.

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: 2

🤖 Fix all issues with AI agents
In `@src/components/tabs/pid-tuning/RatesSubTab.vue`:
- Around line 391-397: The recursive initModel retry timer can outlive the
component and call getBoundingClientRect on null refs; assign the setTimeout id
to rcUpdateInterval whenever scheduling retries inside initModel, add guards at
the top of initModel (and any DOM access sites) to return early if the related
refs are null before calling getBoundingClientRect, and clear the timer on
unmount (and also cancel animationFrameId and stop rendering by setting
keepRendering = false) in the component's beforeUnmount/unmounted hook so
rcUpdateInterval is cleared and no further retries run after refs are nulled.

In `@src/components/tabs/PidTuningTab.vue`:
- Around line 253-263: The three no-op functions copyProfile, copyRateProfile,
and resetProfile are hooked to header buttons but do nothing; restore the legacy
behaviors by porting the dialog and action logic from the previous tab
implementation: implement copyProfile to open the profile-copy dialog and
dispatch the same store action or emit the same event used in the legacy tab,
implement copyRateProfile to open the rate-profile-copy dialog and perform the
same copy routine, and implement resetProfile to show the reset confirmation
dialog and call the reset action on confirm; if you cannot port the dialogs now,
disable or hide the header buttons instead so users cannot trigger these TODO
functions. Ensure you reference and reuse the legacy dialog component names and
store action names when wiring these functions.

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

🤖 Fix all issues with AI agents
In `@src/components/tabs/pid-tuning/RatesSubTab.vue`:
- Around line 391-396: The code reuses rcUpdateInterval for both init retry
timeouts and the recurring RC update interval, which can orphan a pending
timeout; fix by introducing a separate variable (e.g., initModelTimeoutId) used
exclusively by initModel() for setTimeout retries and leave rcUpdateInterval for
the setInterval RC updates (references: rcUpdateInterval, initModel,
animationFrameId, keepRendering); ensure initModel stores timeouts in
initModelTimeoutId, clear/initModelTimeoutId on successful init and in the
component unmount/cleanup path, and only clear rcUpdateInterval where you stop
the regular RC updates.
- Around line 979-996: The code currently calls balloon.draw() twice by
iterating over balloons before and after drawAngleModeLabels; remove the first
loop (the for (const balloon of balloons) { balloon.draw(); } that appears
before the drawAngleModeLabels call) so each balloon is rendered only once,
keeping the single draw loop that follows drawAngleModeLabels; this touches the
balloons array iteration and uses symbols balloon.draw(), drawAngleModeLabels,
isBetaflightRates, ratesType, balloonsDirty, ctx, canvas, and rates.

In `@src/components/tabs/PidTuningTab.vue`:
- Around line 253-285: The copyProfile function is using the undefined variable
profile.value; replace all uses of profile.value with currentProfile.value
(e.g., in the exclusion check when building options, and when setting
FC.COPY_PROFILE.srcProfile and the console log). Ensure copyProfile still sets
FC.COPY_PROFILE.type = 0 and FC.COPY_PROFILE.dstProfile = targetProfile, and
that the MSP call (MSP.promise with MSPCodes.MSP_COPY_PROFILE and
mspHelper.crunch) remains unchanged.
- Around line 287-319: The copyRateProfile function incorrectly references
rateProfile.value (undefined) instead of the actual reactive/current value
currentRateProfile; update all occurrences of rateProfile.value in
copyRateProfile to use currentRateProfile (or currentRateProfile.value if it is
a ref), e.g., when building options exclusion, setting
FC.COPY_PROFILE.srcProfile, logging the copy operation, and anywhere else in
that function; ensure FC.COPY_PROFILE assignment still uses the numeric profile
index and keep the existing MSP.promise(MSPCodes.MSP_COPY_PROFILE,
mspHelper.crunch(...)) call and error handling unchanged.

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

🤖 Fix all issues with AI agents
In `@src/components/tabs/pid-tuning/RatesSubTab.vue`:
- Around line 1584-1591: Track and clear the nested 100ms setTimeout so its
callback won't run after unmount: store the timeout id returned by setTimeout in
a variable (e.g., modelInitTimeout) when scheduling the callback that adds
window.addEventListener("resize", handleModelResize) and starts the animation
loop (sets lastTimestamp and animationFrameId via
requestAnimationFrame(renderModel)); then in the component teardown
(onUnmounted) clearTimeout(modelInitTimeout), remove the resize listener
(handleModelResize), and cancelAnimationFrame(animationFrameId) to fully clean
up.
🧹 Nitpick comments (3)
src/components/tabs/pid-tuning/RatesSubTab.vue (2)

1438-1443: Empty function can be removed or implemented.

draw3DRatesPreview() is defined but only contains a comment. If it's not needed, consider removing it to avoid confusion.


1520-1521: Remove duplicate comment.

Lines 1520-1521 have the same comment "// Watch for changes and redraw" repeated.

🧹 Suggested fix
-// Watch for changes and redraw
 // Watch for changes and redraw
 watch([ratesType, rcRate, rcRatePitch, rcRateYaw, rollRate, pitchRate, yawRate, rcExpo, rcPitchExpo, rcYawExpo], () => {
src/components/tabs/PidTuningTab.vue (1)

447-456: Unused cleanup function is dead code.

The cleanup function is defined but never exposed via defineExpose or called. The comment at line 454-455 confirms defineExpose isn't used. Either remove this function or expose it if it's needed by gui.js tab_switch_cleanup.

🧹 Suggested fix - remove unused function
-// Cleanup callback - called from gui.js tab_switch_cleanup when switching away from this tab
-function cleanup(callback) {
-    // Any cleanup needed before unmounting
-    // Call the callback to signal cleanup is complete
-    if (callback) callback();
-}
-
-// No need to expose methods - using global reactive state via tabState
-// defineExpose is not needed anymore

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

🤖 Fix all issues with AI agents
In `@src/components/tabs/pid-tuning/RatesSubTab.vue`:
- Around line 436-440: The computed showMaxRateWarning currently returns false;
change it to return true when any of the maxAngularVelRoll, maxAngularVelPitch,
or maxAngularVelYaw values exceed 1800 (matching the legacy check in
src/js/tabs/pid_tuning.js), e.g., compute and return (maxAngularVelRoll > 1800
|| maxAngularVelPitch > 1800 || maxAngularVelYaw > 1800) so the template warning
displays when an axis angular velocity exceeds 1800°/s.

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

🤖 Fix all issues with AI agents
In `@src/components/tabs/pid-tuning/RatesSubTab.vue`:
- Around line 436-443: showMaxRateWarning uses parseInt on
maxAngularVelRoll/maxAngularVelPitch/maxAngularVelYaw which return "" for
Betaflight rates, producing NaN and silencing the warning; update
showMaxRateWarning to use the numeric max-angular-velocity values instead
(either compute numeric velocities directly inside the computed or reference
existing numeric computed/methods that produce degrees/sec regardless of
isBetaflightRates), e.g., derive numericMaxAngularVelRoll/Pitch/Yaw or expose
the numeric computed for use here and then compare those against
MAX_RATE_WARNING; apply the same change to the other occurrences that check max
angular velocities (the similar logic around the later block noted in the
review).

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

🤖 Fix all issues with AI agents
In `@src/components/tabs/pid-tuning/RatesSubTab.vue`:
- Around line 1627-1643: The untracked setTimeout call used to delay the initial
draw should be assigned to a variable (e.g., initTimeout) so it can be cleared
on component teardown; wrap the existing setTimeout(...) invocation (which calls
nextTick -> drawRateCurves, drawThrottleCurve, updateRatesLabels) into a tracked
timer and add an onUnmounted handler that calls clearTimeout(initTimeout) to
prevent the callback from running after unmount; keep the existing immediate
nextTick call as-is and rely on the existing null guards in
drawRateCurves/drawThrottleCurve/updateRatesLabels.
🧹 Nitpick comments (2)
src/components/tabs/pid-tuning/RatesSubTab.vue (2)

1456-1461: Unused function draw3DRatesPreview can be removed.

This function is defined but never called anywhere in the component. The 3D preview rendering is handled by renderModel() instead. Consider removing this dead code to reduce confusion.

🧹 Suggested removal
-function draw3DRatesPreview() {
-    if (!ratesPreviewCanvas.value || !model) return;
-
-    // This function is called continuously via requestAnimationFrame
-    // It rotates the 3D model based on current RC input
-}
-

1538-1539: Duplicate comment can be removed.

Line 1538 duplicates the comment on line 1539.

🧹 Suggested fix
-// Watch for changes and redraw
 // Watch for changes and redraw
 watch([ratesType, rcRate, rcRatePitch, rcRateYaw, rollRate, pitchRate, yawRate, rcExpo, rcPitchExpo, rcYawExpo], () => {

- Replace hard-coded UI strings in Gyro Lowpass blocks with (...) for translation support
- Ensures all user-facing labels use i18n keys for consistent localization
@sonarqubecloud
Copy link

@github-actions
Copy link
Contributor

🎉 Do you want to test this code? 🎉

⚠️ CAUTION: The build may be unstable and result in corrupted configurations or data loss. Use only for testing! ⚠️

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Status: App

Development

Successfully merging this pull request may close these issues.

2 participants