Skip to content

Conversation

@haslinghuis
Copy link
Member

@haslinghuis haslinghuis commented Oct 20, 2025

Summary by CodeRabbit

  • New Features

    • Added a "Save & Erase" action to save blackbox logs and erase flash in one flow (available for both MSC-supported and not-supported paths).
    • Introduced distinct supported vs. not-supported UI actions for save and erase.
  • Bug Fixes

    • Erase now only runs after a full, valid download and shows a warning if conditions aren't met.
    • Fixed enable/disable behavior for dataflash action controls.
  • Localization

    • Added English translation for "Save & Erase" and removed the deprecated translation entry.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 20, 2025

Walkthrough

Adds a "Save & Erase" UI action and English translation; implements conditional erase that runs only after a complete, validated blackbox download; updates save flow, button handlers, disabled-state selector, and end-of-file/chunk handling.

Changes

Cohort / File(s) Summary
Localization
locales/en/messages.json
Removed dataflashButtonSaveFileDeprecated; added dataflashButtonSaveAndErase with message "Save & Erase" and description.
UI Buttons / Templates
src/tabs/onboard_logging.html
Added save-flash-erase buttons for MSC-supported and MSC-not-supported flows; replaced deprecated save-flash markup; adjusted supported/not-supported button variants and labels.
Download & Erase Logic
src/js/tabs/onboard_logging.js
Added conditionallyEraseFlash(maxBytes, nextAddress); changed flash_save_begin() signature to flash_save_begin(alsoErase = false); wired alsoErase into chunk and end-of-file handling; added save-and-erase click handler; expanded disabled-state selector; replaced isNaN checks with Number.isNaN; safer dialog close check.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant UI as "Onboard UI"
    participant Saver as "flash_save_begin(alsoErase=true)"
    participant File as "Local file"
    participant Eraser as "conditionallyEraseFlash()"

    User->>UI: Click "Save & Erase"
    UI->>Saver: Start save (alsoErase=true)
    loop per chunk
        Saver->>File: Write chunk
        File-->>Saver: Ack
    end
    alt completed and validated download
        Saver->>Eraser: conditionallyEraseFlash(maxBytes, nextAddress)
        Eraser->>Eraser: Verify fullness & continuity
        Eraser-->>UI: Erase result
        UI-->>User: Download + Erase complete
    else incomplete or cancelled
        Saver-->>UI: Log dataflashSaveIncompleteWarning
        UI-->>User: Download complete (no erase)
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Suggested reviewers

  • nerdCopter
  • VitroidFPV

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Description Check ⚠️ Warning The pull request description only includes a reference to “fixes #4669” and lacks any summary of the changes made, implementation details, testing instructions, or context as required by the repository’s description template. Please expand the description to include a concise summary of the changes introduced by this PR, implementation details for the new download-and-erase workflow, any user-visible behavior changes, and relevant testing instructions or notes for reviewers.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (3 passed)
Check name Status Explanation
Title Check ✅ Passed The title succinctly and clearly describes the main change by indicating that a Save and Erase button is being added for the onboard logging feature, which aligns with the primary change in the pull request.
Linked Issues Check ✅ Passed The changes introduce a user-selectable download-and-erase option, integrate an integrity check before erasing via conditionallyEraseFlash, and update the UI with separate Save and Erase controls, which directly fulfill the objectives outlined in issue #4669.
Out of Scope Changes Check ✅ Passed All code and translation changes are directly related to enabling and labeling the new download-and-erase functionality for onboard logging and there are no unrelated modifications outside the objectives of issue #4669.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 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

🧹 Nitpick comments (1)
src/js/tabs/onboard_logging.js (1)

547-549: EOF handling is correct, but consider adding error handling for the erase operation.

The code correctly delegates validation to conditionallyEraseFlash when EOF is reached early. However, neither this path nor the erase command itself includes error handling for MSP command failures.

Consider adding an error callback to handle erase failures:

 function conditionallyEraseFlash(maxBytes, nextAddress) {
     if (!isNaN(maxBytes) && nextAddress >= maxBytes) {
         $(".dataflash-confirm-erase").addClass("erasing");
-        MSP.send_message(MSPCodes.MSP_DATAFLASH_ERASE, false, false, poll_for_erase_completion);
+        MSP.send_message(MSPCodes.MSP_DATAFLASH_ERASE, false, false, poll_for_erase_completion, () => {
+            gui_log(i18n.getMessage("dataflashEraseError") || "Failed to erase flash.");
+            $(".dataflash-confirm-erase").removeClass("erasing");
+        });
     } else {
         gui_log(
             i18n.getMessage("dataflashSaveIncompleteWarning") ||
                 "Downloaded size did not match expected size - not erasing flash.",
         );
     }
 }

Note: This assumes MSP.send_message supports an error callback parameter. Verify the signature before applying this change.

📜 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 b7805d7 and 407b843.

📒 Files selected for processing (3)
  • locales/en/messages.json (1 hunks)
  • src/js/tabs/onboard_logging.js (3 hunks)
  • src/tabs/onboard_logging.html (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/js/tabs/onboard_logging.js (5)
src/js/msp.js (1)
  • MSP (5-455)
src/js/msp/MSPCodes.js (1)
  • MSPCodes (2-211)
src/js/gui_log.js (1)
  • gui_log (7-21)
src/js/receiver_msp/receiver_msp.js (1)
  • i18n (8-8)
src/js/localization.js (1)
  • i18n (7-7)
🔇 Additional comments (3)
locales/en/messages.json (1)

3561-3564: LGTM
String addition is consistent with surrounding keys and follows existing formatting conventions.

src/js/tabs/onboard_logging.js (2)

481-491: Validation logic is sound.

The conditional erase correctly verifies that the download completed by checking both that maxBytes is valid and that nextAddress >= maxBytes. The fallback message ensures graceful degradation if the translation key is missing.


530-533: Correctly implements conditional erase after successful download.

The logic properly checks both cancellation status and the alsoErase flag before triggering the erase operation. Closing the file beforehand avoids potential file handle conflicts.

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

🧹 Nitpick comments (1)
src/js/tabs/onboard_logging.js (1)

548-550: Consider adding !saveCancelled check for consistency.

The erase is triggered when receiving a zero-byte EOF block, but unlike the similar logic at lines 532-533, this doesn't verify that the user hasn't cancelled the operation. While this code path represents a successful download completion, adding the !saveCancelled check would ensure consistency and handle the edge case where a user cancels between receiving the final data block and the EOF marker.

Apply this diff for consistency:

-                                if (alsoErase) {
+                                if (!saveCancelled && alsoErase) {
                                     conditionallyEraseFlash(maxBytes, nextAddress);
                                 }
📜 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 407b843 and 4345031.

📒 Files selected for processing (1)
  • src/js/tabs/onboard_logging.js (5 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/js/tabs/onboard_logging.js (5)
src/js/msp.js (1)
  • MSP (5-455)
src/js/msp/MSPCodes.js (1)
  • MSPCodes (2-211)
src/js/gui_log.js (1)
  • gui_log (7-21)
src/js/receiver_msp/receiver_msp.js (1)
  • i18n (8-8)
src/js/localization.js (1)
  • i18n (7-7)
🔇 Additional comments (4)
src/js/tabs/onboard_logging.js (4)

106-106: LGTM! Click handler for "Save & Erase" properly implemented.

The click handler correctly invokes flash_save_begin(true) to enable the conditional erase flow, addressing the missing handler identified in previous reviews.


482-492: LGTM! Conditional erase logic properly implements integrity check.

The function correctly verifies that the downloaded size (nextAddress) meets the expected size (maxBytes) before erasing the dataflash, addressing the PR objective to ensure download integrity. The warning message with fallback is appropriate for cases where the download is incomplete.


494-494: LGTM! Function signature properly extended with backward compatibility.

The alsoErase parameter with a default value of false maintains backward compatibility for existing callers while enabling the new "Save & Erase" workflow.


531-534: LGTM! Conditional erase correctly integrated after successful download.

The erase logic is properly gated by both !saveCancelled and alsoErase checks, ensuring that erase only occurs when the user hasn't cancelled the operation and has explicitly requested the "Save & Erase" action.

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

📜 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 4345031 and 7f50936.

📒 Files selected for processing (1)
  • src/js/tabs/onboard_logging.js (5 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/js/tabs/onboard_logging.js (4)
src/js/msp/MSPCodes.js (1)
  • MSPCodes (2-211)
src/js/gui_log.js (1)
  • gui_log (7-21)
src/js/receiver_msp/receiver_msp.js (1)
  • i18n (8-8)
src/js/localization.js (1)
  • i18n (7-7)
🔇 Additional comments (2)
src/js/tabs/onboard_logging.js (2)

359-361: LGTM: disabled-state now covers Save, Erase, and Save&Erase.

Selector correctly includes save-flash-erase alongside the others. Nice catch-up with the new button.


493-493: LGTM: erase is gated behind explicit intent and size validation.

The alsoErase flow only triggers after successful completion and defers to conditionallyEraseFlash for integrity checks. This matches the “download and erase only after full, validated download” requirement.

Please confirm the UX expectation: no dialog is shown during Save+Erase, but the erase progress/notification is handled via poll_for_erase_completion. If a visible spinner is desired, consider showing the existing .dataflash-confirm-erase dialog in non-interactive mode during Save+Erase.

Also applies to: 531-534, 547-549

@Pancronos
Copy link

Tested few times, works fine

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/js/tabs/onboard_logging.js (1)

556-558: Consider adding explicit cancellation check for defensive programming.

The conditional erase after EOF is functionally correct, as cancellation prevents further callbacks. However, for clarity and defensive programming, consider adding an explicit !saveCancelled check here to match the pattern at Line 540.

Apply this diff for additional safety:

-                if (alsoErase) {
+                if (!saveCancelled && alsoErase) {
                     conditionallyEraseFlash(maxBytes, nextAddress);
                 }
📜 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 7f50936 and bdd06e9.

📒 Files selected for processing (3)
  • locales/en/messages.json (1 hunks)
  • src/js/tabs/onboard_logging.js (6 hunks)
  • src/tabs/onboard_logging.html (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • locales/en/messages.json
🧰 Additional context used
🧬 Code graph analysis (1)
src/js/tabs/onboard_logging.js (4)
src/js/fc.js (1)
  • FC (132-1006)
src/js/msp.js (1)
  • MSP (5-455)
src/js/msp/MSPCodes.js (1)
  • MSPCodes (2-211)
src/js/gui_log.js (1)
  • gui_log (7-21)
🔇 Additional comments (8)
src/tabs/onboard_logging.html (2)

113-113: LGTM!

The new "Save & Erase" button for the non-MSC path is properly structured with appropriate CSS classes and i18n integration. The placement between the erase and save buttons provides a logical workflow.


115-116: No issues found; translation key exists and is correctly referenced.

The translation key dataflashSaveFileDepreciationHint is correctly implemented and exists consistently across all 19 locale files. The code at lines 115-116 properly references this key, which uses the "Depreciation" spelling throughout the entire translation system—this is intentional, not a typo in the PR.

Likely an incorrect or invalid review comment.

src/js/tabs/onboard_logging.js (6)

105-113: LGTM!

The event handlers are correctly implemented with explicit boolean parameters and proper preventDefault() calls. This ensures the alsoErase parameter receives the intended boolean value rather than a jQuery event object.


367-369: LGTM!

The selector correctly includes all relevant buttons (erase-flash, save-flash-erase, and save-flash) in the disabled state toggle. The logic appropriately disables all actions when FC.DATAFLASH.usedSize === 0.


489-500: LGTM!

The conditionallyEraseFlash function correctly validates that the download completed successfully before erasing. The use of Number.isFinite(maxBytes) is more robust than !isNaN(), and resetting eraseCancelled = false ensures proper polling behavior even after a previous cancellation.


502-502: LGTM!

The function signature update with a default parameter alsoErase = false maintains backward compatibility while enabling the new erase-after-download feature.


522-522: LGTM!

Replacing isNaN() with Number.isNaN() is a best practice improvement that provides stricter type checking without unexpected type coercion.


539-542: LGTM!

The conditional erase logic correctly ensures that flash is only erased when: (1) the download completed successfully (nextAddress >= maxBytes), (2) the user didn't cancel (!saveCancelled), and (3) the erase variant was triggered (alsoErase).

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

Caution

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

⚠️ Outside diff range comments (1)
src/js/tabs/onboard_logging.js (1)

454-467: Fix compression logging guard to handle null and zero safely.

isNaN(null) is false, so the current check logs with null and divides by zero. Use a finite-and-nonzero check.

- if (!isNaN(totalBytesCompressed)) {
+ if (Number.isFinite(totalBytesCompressed) && totalBytesCompressed > 0) {
     console.log(
       "Compressed into",
       totalBytesCompressed,
       "bytes with mean compression factor of",
-      totalBytes / totalBytesCompressed,
+      (totalBytesCompressed > 0 ? totalBytes / totalBytesCompressed : "n/a"),
     );
   }
🧹 Nitpick comments (2)
src/js/tabs/onboard_logging.js (2)

522-529: Use finite checks and guard progress when maxBytes is 0.

Covers Infinity/NaN and avoids invalid progress when invoked despite disabled UI.

- if (Number.isNaN(bytesCompressed) || Number.isNaN(totalBytesCompressed)) {
+ if (!Number.isFinite(bytesCompressed) || !Number.isFinite(totalBytesCompressed)) {
     totalBytesCompressed = null;
   } else {
     totalBytesCompressed += bytesCompressed;
   }
 
- $(".dataflash-saving progress").attr("value", (nextAddress / maxBytes) * 100);
+ const progressPct = maxBytes > 0 ? (nextAddress / maxBytes) * 100 : 0;
+ $(".dataflash-saving progress").attr("value", progressPct);

560-564: Respect cancellation on retry when a bad block is received.

Avoid continuing reads after user cancels.

- // There was an error with the received block (address didn't match the one we asked for), retry
- mspHelper.dataflashRead(nextAddress, self.blockSize, onChunkRead);
+ // Bad block: retry only if not cancelled
+ if (saveCancelled) {
+   dismiss_saving_dialog();
+   FileSystem.closeFile(openedFile);
+   return;
+ }
+ mspHelper.dataflashRead(nextAddress, self.blockSize, onChunkRead);
📜 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 bdd06e9 and 9380578.

📒 Files selected for processing (1)
  • src/js/tabs/onboard_logging.js (6 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/js/tabs/onboard_logging.js (5)
src/js/fc.js (1)
  • FC (132-1006)
src/js/msp.js (1)
  • MSP (5-455)
src/js/msp/MSPCodes.js (1)
  • MSPCodes (2-211)
src/js/gui_log.js (1)
  • gui_log (7-21)
src/js/localization.js (1)
  • i18n (7-7)
⏰ 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
🔇 Additional comments (4)
src/js/tabs/onboard_logging.js (4)

367-369: LGTM: disabled-state now covers Save & Erase.

Selector includes erase-flash, save-flash, and save-flash-erase consistently.


502-502: LGTM: explicit default prevents accidental truthy-arg erase.

alsoErase = false avoids the classic event-object-as-truthy pitfall.


540-543: LGTM: conditional erase only after verified full download.

The erase trigger is gated behind size verification and cancellation checks.

Also applies to: 556-558


105-113: Review comment is incorrect; CSS already prevents clicks on disabled anchors.

The stylesheet src/css/main.less (line 47–48) defines a.disabled { pointer-events: none; } globally, which prevents pointer events on all anchor elements with the .disabled class. The .save-flash and .save-flash-erase anchors inherit this rule, so clicks are already blocked at the browser level before the event handler runs. The suggested JavaScript guard is redundant.

Likely an incorrect or invalid review comment.

@sonarqubecloud
Copy link

@github-actions
Copy link
Contributor

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

Caution

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

⚠️ Outside diff range comments (2)
src/js/tabs/onboard_logging.js (2)

583-599: Fix malformed closing span tag

Update the closing span from </span class="message-negative> to </span>:

-                    })}</span class="message-negative></strong>`,
+                    })}</span></strong>`,

531-553: Add catch handler to FileSystem.writeChunck promise
FileSystem.writeChunck(...) currently lacks a .catch, leading to unhandled rejections and potential dangling dialogs/files on I/O failure—add a .catch to set self.writeError, log the error, dismiss the dialog, and close the file.

- FileSystem.writeChunck(openedFile, blob).then(() => {
+ FileSystem.writeChunck(openedFile, blob)
+     .then(() => {
         // existing success handling...
     })
+    .catch(err => {
+        self.writeError = true;
+        console.error("Error writing blackbox chunk:", err);
+        gui_log(i18n.getMessage("dataflashFileWriteFailed"));
+        dismiss_saving_dialog();
+        FileSystem.closeFile(openedFile);
+    });
🧹 Nitpick comments (3)
src/js/tabs/onboard_logging.js (3)

522-526: Harden bytesCompressed handling to avoid NaN propagation

Firmware may omit or vary bytesCompressed (undefined/Infinity). Make the guard type- and range-safe.

-                                if (Number.isNaN(bytesCompressed) || Number.isNaN(totalBytesCompressed)) {
-                                    totalBytesCompressed = null;
-                                } else {
-                                    totalBytesCompressed += bytesCompressed;
-                                }
+                                // Robustly handle missing/invalid compression metrics
+                                if (
+                                    totalBytesCompressed === null ||
+                                    typeof bytesCompressed !== "number" ||
+                                    !Number.isFinite(bytesCompressed)
+                                ) {
+                                    totalBytesCompressed = null;
+                                } else {
+                                    totalBytesCompressed += bytesCompressed;
+                                }

453-467: Avoid logging Infinity/incorrect stats when compression is unknown

When totalBytesCompressed becomes null, isNaN(null) is false → logs “Infinity”. Gate on finite, >0.

-        if (!isNaN(totalBytesCompressed)) {
+        if (typeof totalBytesCompressed === "number" && Number.isFinite(totalBytesCompressed) && totalBytesCompressed > 0) {
             console.log(
                 "Compressed into",
                 totalBytesCompressed,
                 "bytes with mean compression factor of",
-                totalBytes / totalBytesCompressed,
+                (totalBytes / totalBytesCompressed).toFixed(2),
             );
         }

517-527: Early-exit on cancel to stop I/O ASAP

Skip blob writes when user has already cancelled; reduces I/O and latency to close dialog.

-                    function onChunkRead(chunkAddress, chunkDataView, bytesCompressed) {
+                    function onChunkRead(chunkAddress, chunkDataView, bytesCompressed) {
+                        if (saveCancelled) {
+                            dismiss_saving_dialog();
+                            if (openedFile) FileSystem.closeFile(openedFile);
+                            return;
+                        }
📜 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 9380578 and 888ff0b.

📒 Files selected for processing (1)
  • src/js/tabs/onboard_logging.js (7 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/js/tabs/onboard_logging.js (4)
src/js/fc.js (1)
  • FC (132-1006)
src/js/msp/MSPCodes.js (1)
  • MSPCodes (2-211)
src/js/gui_log.js (1)
  • gui_log (7-21)
src/js/localization.js (1)
  • i18n (7-7)
🔇 Additional comments (4)
src/js/tabs/onboard_logging.js (4)

105-113: Save/Save+Erase handlers: explicit boolean + preventDefault — LGTM

Correctly avoids event→truthy coercion and unintended erase; good separation of actions.


367-369: Disabled-state now includes Save+Erase — LGTM

Selector covers erase-flash, save-flash-erase, and MSC save-flash when no data present.


489-500: Conditional erase only after full verified download — LGTM

Finite/max-bytes check before erase is sound; integrates cleanly with notifications.


609-617: Guarded dialog.close() after auto-erase — LGTM

Prevents InvalidStateError when erase dialog was never opened in Save+Erase flow.

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.

Add BlackBox download and erase option

3 participants