Skip to content

The min-max control context menu at the chart config dialog#918

Merged
haslinghuis merged 22 commits into
betaflight:masterfrom
demvlad:minmax_ctrl_menu
May 20, 2026
Merged

The min-max control context menu at the chart config dialog#918
haslinghuis merged 22 commits into
betaflight:masterfrom
demvlad:minmax_ctrl_menu

Conversation

@demvlad

@demvlad demvlad commented May 15, 2026

Copy link
Copy Markdown
Contributor

Added min-max control context menu

min-max-ctrl-menu

Summary by CodeRabbit

  • New Features
    • Right-click context menus on Min/Max inputs provide bulk actions: reset to field defaults, copy a field’s range to all (when both bounds set), center ranges symmetrically, apply a single uniform scale, zoom around midpoints, or set each field to its full historical range. Menu highlights the current field.
  • Bug Fixes
    • Min/Max inputs validate numeric input and only apply finite values.
  • UI/UX
    • Modal permits visible overflow and adds a dedicated menu container so context menus render correctly; double-click resets only the targeted bound.

Review Change Stack

@coderabbitai

coderabbitai Bot commented May 15, 2026

Copy link
Copy Markdown

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 header portal and context-menu wrappers for per-field Min/Max inputs, hardens setMin/setMax numeric parsing, and introduces currentState plus bulk and per-field Min/Max helper actions exposed via a hierarchical context menu.

Changes

Graph Min/Max Context Menu

Layer / File(s) Summary
Modal overflow and portal mount
src/components/GraphConfigDialog.vue
Modal receives overflow-visible and the header gains div#menu-portal-container as a portal target for context menus.
Per-field Min/Max input context menu wiring
src/components/GraphConfigDialog.vue
Each field's Min/Max UInputNumber is wrapped by UContextMenu that portals to #menu-portal-container; right-click sets currentState.graph/currentState.field, values flow through setMin/setMax, and double-click resets the individual bound.
Numeric parsing for setMin/setMax
src/components/GraphConfigDialog.vue
setMin/setMax now use Number.parseFloat and assign only when the parsed value is finite to avoid invalid NaN assignments.
Context state and bulk Min/Max handlers
src/components/GraphConfigDialog.vue
Adds currentState and bulk/per-field helpers: reset defaults, copy like-this, center on zero, one shared scale, zoom in/out around midpoints, and apply full all-time field ranges via props.flightLog.getMinMaxForFieldDuringAllTime; builds menuItems including a current-field submenu whose label is updated in onContextMenu.

🎯 4 (Complex) | ⏱️ ~45 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning The PR description is minimal and does not follow the repository's template structure. Required sections are missing. Expand the description to include: overview of changes, motivation/why these changes were made, how to test the feature, and any relevant issue references (e.g., 'Fixes #...'). Follow the template guidelines provided.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly describes the main feature added: a context menu for min-max controls in the chart config dialog, which matches the primary change in the changeset.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🧹 Nitpick comments (2)
src/components/GraphConfigDialog.vue (2)

711-714: 💤 Low value

Duplicate separator in menu items.

Two consecutive separators serve no visual purpose.

♻️ Proposed fix
   {
     label: 'Default',
     onSelect() {
       setMinMaxToDefault();
     }
   },
   {
     type: 'separator',
   },
-  {
-    type: 'separator',
-  },
   {
     label: '\u25BCClose',
   },
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/GraphConfigDialog.vue` around lines 711 - 714, The menu items
array in GraphConfigDialog.vue contains two consecutive entries with type:
'separator', which is redundant; remove one of the duplicate separator objects
(or add a small dedupe step when building the menuItems array) so there are no
adjacent items whose only property is type: 'separator'—look for the menu array
or computed property that constructs the menu (the objects with type:
'separator') and delete the extra separator or filter out consecutive separators
before rendering.

717-718: 💤 Low value

Consider clarifying the "Close" menu item purpose.

The "Close" item has a Unicode down-arrow character (\u25BC) but no onSelect handler. If this is intended to close the menu automatically (default behavior), consider removing the down-arrow character for clarity. If it needs a specific action, add an onSelect handler.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/GraphConfigDialog.vue` around lines 717 - 718, The menu item
currently defined as label: '\u25BCClose' is ambiguous because it shows a
down-arrow but has no onSelect handler; either remove the Unicode arrow from the
label to avoid implying extra behavior (change label to "Close") or add an
explicit onSelect property on the same menu item object (e.g., onSelect: () => {
/* call the dialog/menu close function */ }) so it performs the intended close
action; update the menu item definition where label: '\u25BCClose' is declared
and ensure you reference the dialog/menu close function you use elsewhere (or
call the component's close handler) in the added onSelect.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/components/GraphConfigDialog.vue`:
- Around line 618-625: The function setMinMaxToDefault updates fields via
resetMin(field) and resetMax(field) but never notifies the parent; after the
loop (or after modifying each field) call emitUpdate() so the updated
currentState.graph changes propagate; locate setMinMaxToDefault and add a single
emitUpdate() invocation (or ensure existing emitUpdate helper is used) to
trigger the parent update after resetting fields.
- Around line 638-649: setMinMaxCentered currently reads field.curve.MinMax
without guarding and doesn't call emitUpdate(); update it to first check that
currentState.graph exists and that each field.curve and field.curve.MinMax exist
(e.g., guard with if (!field.curve || !field.curve.MinMax) continue) before
reading MinMax.min/Max, then compute min/max and call setMin(field, min) and
setMax(field, max) as before, and finally call emitUpdate() once after the loop
to notify changes; reference setMinMaxCentered, currentState.graph,
field.curve.MinMax, setMin, setMax, and emitUpdate in your changes.
- Around line 651-665: setMinMaxOneScale currently reads field.curve.MinMax
without guarding and doesn’t notify changes; update it to first check that
currentState.graph exists and that each field has a curve and a MinMax object
(e.g., guard with field.curve && field.curve.MinMax or use optional chaining)
before accessing min/max, skip fields that lack the structure, then call
setMin(field, min) and setMax(field, max) as before and finally call
emitUpdate() after the loop to propagate the changes (referenced symbols:
setMinMaxOneScale, currentState.graph, field.curve.MinMax, setMin, setMax,
emitUpdate).
- Around line 627-636: In setMinMaxLikeThis, guard against missing nested data
and call emitUpdate after mutating fields: check that currentState.field and
currentState.field.curve and currentState.field.curve.MinMax exist (or use
optional chaining) before reading MinMax.min and MinMax.max, bail out early if
absent, then loop over currentState.graph.fields calling setMin(field, min) and
setMax(field, max) as before, and finally invoke emitUpdate() so the changes
propagate; reference symbols: setMinMaxLikeThis,
currentState.field.curve.MinMax, setMin, setMax, emitUpdate.

---

Nitpick comments:
In `@src/components/GraphConfigDialog.vue`:
- Around line 711-714: The menu items array in GraphConfigDialog.vue contains
two consecutive entries with type: 'separator', which is redundant; remove one
of the duplicate separator objects (or add a small dedupe step when building the
menuItems array) so there are no adjacent items whose only property is type:
'separator'—look for the menu array or computed property that constructs the
menu (the objects with type: 'separator') and delete the extra separator or
filter out consecutive separators before rendering.
- Around line 717-718: The menu item currently defined as label: '\u25BCClose'
is ambiguous because it shows a down-arrow but has no onSelect handler; either
remove the Unicode arrow from the label to avoid implying extra behavior (change
label to "Close") or add an explicit onSelect property on the same menu item
object (e.g., onSelect: () => { /* call the dialog/menu close function */ }) so
it performs the intended close action; update the menu item definition where
label: '\u25BCClose' is declared and ensure you reference the dialog/menu close
function you use elsewhere (or call the component's close handler) in the added
onSelect.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 0dc53e3c-a62d-47f3-910c-4710c6b14f9e

📥 Commits

Reviewing files that changed from the base of the PR and between 80581ee and c9f5c02.

📒 Files selected for processing (1)
  • src/components/GraphConfigDialog.vue

Comment thread src/components/GraphConfigDialog.vue
Comment thread src/components/GraphConfigDialog.vue Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

♻️ Duplicate comments (1)
src/components/GraphConfigDialog.vue (1)

628-631: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Guard nested curve.MinMax reads before bulk actions.

At Line 630, Line 643, and Line 659, the code reads field.curve.MinMax without guards. If any field is missing that structure, these menu actions will throw and abort.

🔧 Suggested fix
 function setMinMaxLikeThis() {
   if (currentState.graph && currentState.field) {
-    const min = currentState.field.curve.MinMax.min;
-    const max = currentState.field.curve.MinMax.max;
+    ensureCurveMinMax(currentState.field);
+    const min = currentState.field.curve.MinMax.min ?? -500;
+    const max = currentState.field.curve.MinMax.max ?? 500;
     for (const field of currentState.graph.fields) {
       setMin(field, min);
       setMax(field, max);
     }
     emitUpdate();
   }
 }

 function setMinMaxCentered() {
   if (currentState.graph) {
     for (const field of currentState.graph.fields) {
-      let min = field.curve.MinMax.min;
-      let max = field.curve.MinMax.max;
+      ensureCurveMinMax(field);
+      let min = field.curve.MinMax.min ?? -500;
+      let max = field.curve.MinMax.max ?? 500;
       max = Math.max(Math.abs(min), Math.abs(max));
       min = -max;
       setMin(field, min);
       setMax(field, max);
     }
     emitUpdate();
   }
 }

 function setMinMaxOneScale() {
   let max = -Number.MAX_VALUE,
       min = Number.MAX_VALUE;
   if (currentState.graph) {
     for (const field of currentState.graph.fields) {
-      max = Math.max(max, Math.max(Math.abs(field.curve.MinMax.min), Math.abs(field.curve.MinMax.max)));
+      ensureCurveMinMax(field);
+      const fMin = field.curve.MinMax.min ?? -500;
+      const fMax = field.curve.MinMax.max ?? 500;
+      max = Math.max(max, Math.max(Math.abs(fMin), Math.abs(fMax)));
     }
     min = -max;

Also applies to: 640-645, 654-660

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/GraphConfigDialog.vue` around lines 628 - 631, The menu
actions read currentState.field.curve.MinMax directly (e.g., in
setMinMaxLikeThis) and can throw if field/curve/MinMax is missing; guard these
accesses by checking currentState.field && currentState.field.curve &&
currentState.field.curve.MinMax (or use optional chaining
currentState.field?.curve?.MinMax) before reading .min/.max, return early or
disable the action when absent, and apply the same change to the other functions
that read field.curve.MinMax at the locations referenced (the other menu action
handlers around lines ~643 and ~659).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Duplicate comments:
In `@src/components/GraphConfigDialog.vue`:
- Around line 628-631: The menu actions read currentState.field.curve.MinMax
directly (e.g., in setMinMaxLikeThis) and can throw if field/curve/MinMax is
missing; guard these accesses by checking currentState.field &&
currentState.field.curve && currentState.field.curve.MinMax (or use optional
chaining currentState.field?.curve?.MinMax) before reading .min/.max, return
early or disable the action when absent, and apply the same change to the other
functions that read field.curve.MinMax at the locations referenced (the other
menu action handlers around lines ~643 and ~659).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 4d7e5256-c1ac-45e4-befe-564f5f1fe67c

📥 Commits

Reviewing files that changed from the base of the PR and between c9f5c02 and 65491dd.

📒 Files selected for processing (1)
  • src/components/GraphConfigDialog.vue

coderabbitai[bot]
coderabbitai Bot previously approved these changes May 16, 2026
haslinghuis
haslinghuis previously approved these changes May 16, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (2)
src/components/GraphConfigDialog.vue (2)

640-652: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Undefined MinMax values produce NaN.

Lines 643-644 read field.curve?.MinMax?.min/max without fallbacks. If any field's MinMax is undefined, Math.abs(undefined) returns NaN, which propagates through the calculation: max = Math.max(NaN, NaN) = NaN, min = -NaN = NaN. All fields end up with NaN min/max values.

🛡️ Proposed fix
 function setMinMaxCentered() {
   if (currentState.graph) {
     for (const field of currentState.graph.fields) {
+      ensureCurveMinMax(field);
       let min = field.curve.MinMax.min;
       let max = field.curve.MinMax.max;
       max = Math.max(Math.abs(min), Math.abs(max));
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/GraphConfigDialog.vue` around lines 640 - 652, The function
setMinMaxCentered currently reads field.curve?.MinMax?.min/max without fallbacks
which yields NaN when undefined; update setMinMaxCentered to guard those
accesses (e.g. read const rawMin = field.curve?.MinMax?.min ?? 0 and const
rawMax = field.curve?.MinMax?.max ?? 0 or skip the field if both are undefined),
then compute max = Math.max(Math.abs(rawMin), Math.abs(rawMax)), min = -max, and
call setMin(field, min) and setMax(field, max) before emitUpdate to avoid
propagating NaN; target the setMinMaxCentered function and the
field.curve?.MinMax?.min/max reads, leaving setMin, setMax, and emitUpdate
intact.

654-669: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Undefined MinMax values produce NaN.

Line 659 computes Math.abs(field.curve?.MinMax?.min) and Math.abs(field.curve?.MinMax?.max) without fallbacks. If any field's MinMax is undefined, Math.abs(undefined) returns NaN, and Math.max(..., NaN) returns NaN. The final min and max will be NaN, corrupting all fields.

🛡️ Proposed fix
 function setMinMaxOneScale() {
   let max = -Number.MAX_VALUE;
   let min;
   if (currentState.graph) {
     for (const field of currentState.graph.fields) {
+      ensureCurveMinMax(field);
       max = Math.max(max, Math.max(Math.abs(field.curve.MinMax.min), Math.abs(field.curve.MinMax.max)));
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/GraphConfigDialog.vue` around lines 654 - 669, In
setMinMaxOneScale, accessing Math.abs(field.curve?.MinMax?.min/max) can produce
NaN when MinMax is undefined; update the loop to only consider numeric finite
values (e.g., check that field.curve?.MinMax?.min/max are != null and isFinite)
or coerce with a numeric fallback before Math.abs, initialize max to -Infinity
instead of -Number.MAX_VALUE, compute max as the maximum of only valid values,
then derive min = -max and call setMin(field, min)/setMax(field, max) as before;
reference symbols: setMinMaxOneScale, currentState.graph.fields,
field.curve?.MinMax, setMin, setMax, emitUpdate.
🧹 Nitpick comments (2)
src/components/GraphConfigDialog.vue (2)

31-31: 💤 Low value

Unused portal container.

The menu-portal-container div is not used because both UContextMenu instances set :portal="false" (lines 163, 182), so menus render in-place rather than being portaled. Either remove this div or document why it's reserved for future use.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/GraphConfigDialog.vue` at line 31, The <div
id="menu-portal-container"></div> is unused because both UContextMenu instances
set :portal="false"; either remove this unused DOM node from the template or
make the portal active: set :portal="true" on the UContextMenu components
(references: UContextMenu instances around the menu definitions) and ensure the
container id "menu-portal-container" remains in the template; if you keep the
div for future use, add a short comment in the template explaining it's reserved
for portaled menus so it's not mistaken as dead code.

717-719: 💤 Low value

Duplicate separators.

Lines 717–718 define two consecutive separator items with no menu items between them. This creates redundant visual spacing.

🧹 Proposed fix
   {
     type: 'separator',
   },
-  {
-    type: 'separator',
-  },
   {
     label: '\u25BCClose',
   },
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/GraphConfigDialog.vue` around lines 717 - 719, There are two
consecutive menu separator objects (type: 'separator') in the menu/items array
inside GraphConfigDialog.vue, creating redundant spacing; remove the duplicate
separator or add a small dedupe check so you never push two adjacent { type:
'separator' } entries (e.g., when building the menu array, skip adding a
separator if the last item is already a separator) to ensure only single
separators remain between groups.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/components/GraphConfigDialog.vue`:
- Around line 628-638: The code in setMinMaxLikeThis reads
currentState.field.curve?.MinMax?.min/max without fallbacks, so undefined flows
into setMin/setMax and becomes NaN; change setMinMaxLikeThis to first read the
raw values (e.g., const minRaw = currentState.field.curve?.MinMax?.min; const
maxRaw = currentState.field.curve?.MinMax?.max), convert/validate them (e.g.,
const min = minRaw !== undefined && !Number.isNaN(Number(minRaw)) ?
Number(minRaw) : undefined), and only call setMin(field, min) or setMax(field,
max) when the validated value is a finite number (or provide a safe default)
before emitting via emitUpdate; reference setMinMaxLikeThis, currentState,
MinMax, setMin, setMax, and emitUpdate when locating the code.

---

Duplicate comments:
In `@src/components/GraphConfigDialog.vue`:
- Around line 640-652: The function setMinMaxCentered currently reads
field.curve?.MinMax?.min/max without fallbacks which yields NaN when undefined;
update setMinMaxCentered to guard those accesses (e.g. read const rawMin =
field.curve?.MinMax?.min ?? 0 and const rawMax = field.curve?.MinMax?.max ?? 0
or skip the field if both are undefined), then compute max =
Math.max(Math.abs(rawMin), Math.abs(rawMax)), min = -max, and call setMin(field,
min) and setMax(field, max) before emitUpdate to avoid propagating NaN; target
the setMinMaxCentered function and the field.curve?.MinMax?.min/max reads,
leaving setMin, setMax, and emitUpdate intact.
- Around line 654-669: In setMinMaxOneScale, accessing
Math.abs(field.curve?.MinMax?.min/max) can produce NaN when MinMax is undefined;
update the loop to only consider numeric finite values (e.g., check that
field.curve?.MinMax?.min/max are != null and isFinite) or coerce with a numeric
fallback before Math.abs, initialize max to -Infinity instead of
-Number.MAX_VALUE, compute max as the maximum of only valid values, then derive
min = -max and call setMin(field, min)/setMax(field, max) as before; reference
symbols: setMinMaxOneScale, currentState.graph.fields, field.curve?.MinMax,
setMin, setMax, emitUpdate.

---

Nitpick comments:
In `@src/components/GraphConfigDialog.vue`:
- Line 31: The <div id="menu-portal-container"></div> is unused because both
UContextMenu instances set :portal="false"; either remove this unused DOM node
from the template or make the portal active: set :portal="true" on the
UContextMenu components (references: UContextMenu instances around the menu
definitions) and ensure the container id "menu-portal-container" remains in the
template; if you keep the div for future use, add a short comment in the
template explaining it's reserved for portaled menus so it's not mistaken as
dead code.
- Around line 717-719: There are two consecutive menu separator objects (type:
'separator') in the menu/items array inside GraphConfigDialog.vue, creating
redundant spacing; remove the duplicate separator or add a small dedupe check so
you never push two adjacent { type: 'separator' } entries (e.g., when building
the menu array, skip adding a separator if the last item is already a separator)
to ensure only single separators remain between groups.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: db552e92-1a65-4abd-a64a-d551437e35e9

📥 Commits

Reviewing files that changed from the base of the PR and between 65491dd and 565ed54.

⛔ Files ignored due to path filters (1)
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (1)
  • src/components/GraphConfigDialog.vue

Comment thread src/components/GraphConfigDialog.vue
@haslinghuis

Copy link
Copy Markdown
Member

Please

  • use npm instead of yarn.
  • remove the yarn.lock file

@haslinghuis

Copy link
Copy Markdown
Member

@demvlad please check the bot to satisfy

coderabbitai[bot]
coderabbitai Bot previously approved these changes May 16, 2026
@haslinghuis

Copy link
Copy Markdown
Member

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented May 16, 2026

Copy link
Copy Markdown
✅ Actions performed

Full review triggered.

haslinghuis
haslinghuis previously approved these changes May 16, 2026
@demvlad demvlad requested a review from haslinghuis May 18, 2026 16:28
haslinghuis
haslinghuis previously approved these changes May 18, 2026
@demvlad demvlad marked this pull request as draft May 19, 2026 04:20
@demvlad

demvlad commented May 19, 2026

Copy link
Copy Markdown
Contributor Author

Will try to use drop menu instead of context.
The drop menu has switches, what can be set without closing menu. And maybe it has more manual control..

@demvlad demvlad marked this pull request as ready for review May 19, 2026 07:11
@demvlad demvlad marked this pull request as draft May 19, 2026 08:02
@demvlad

demvlad commented May 19, 2026

Copy link
Copy Markdown
Contributor Author

The simple PreventDefault solved everything:)

@demvlad demvlad dismissed stale reviews from haslinghuis and coderabbitai[bot] via d498935 May 19, 2026 14:16
@demvlad demvlad marked this pull request as ready for review May 19, 2026 14:18
@demvlad demvlad requested a review from haslinghuis May 19, 2026 14:19
coderabbitai[bot]
coderabbitai Bot previously approved these changes May 19, 2026
@haslinghuis

Copy link
Copy Markdown
Member

PR #918 Review

Summary: Adds a right-click context menu on Min/Max inputs in the graph config dialog, providing bulk actions (reset to defaults, copy range, center, one scale, zoom, full range) for all fields or a single selected field.

Issues Found

1. Mutable shared state via currentState object (Bug-prone)

Lines 613-616: currentState is a plain object mutated in onContextMenu and read by all menu action handlers. This is a race condition waiting to happen — if the menu items are evaluated after the reactive cycle, currentState could be stale or null.

Fix: Use ref() or reactive() so Vue tracks changes:

const currentState = reactive({ graph: null, field: null });

2. Duplicate UContextMenu wrappers (DRY violation)

Lines 163-202: The min and max inputs each get their own <UContextMenu> with identical :items, :portal, and :ui props. Both trigger the same onContextMenu. This means right-clicking either input opens the same menu with the same behavior — the duplication is unnecessary.

Suggestion: Wrap both inputs in a single <UContextMenu> instead of two separate ones.

3. menuItems is not reactive but mutated at runtime

Line 853: onContextMenu mutates menuItems[menuItems.length - 1][0].label to update the submenu label with the field name. Since menuItems is a plain array (not ref/reactive), Vue won't detect this mutation. The label update may not render.

Fix: Make menuItems a computed() that derives from currentState, or use ref() and replace the array.

4. zoom constant compared with != 0 (dead code)

Lines 779, 808: const zoom = 1.1 is a constant, so if (zoom != 0) is always true. This guard is meaningless.

Fix: Remove the dead if check, or make zoom configurable if that's the intent.

5. Loose equality != used instead of !==

Lines 674, 779, 808: Uses != (loose equality). The project likely enforces ===/!== via ESLint.

Fix: Use !== consistently.

6. Missing space in if statement

Line 674: if(min != Number.MAX_VALUE) — missing space after if.

7. overflow-visible on UModal may cause visual glitches

Line 2: Adding class="overflow-visible" to the modal is a broad style override to make the context menu render outside the modal bounds. The portal="#menu-portal-container" approach (line 31) is a better solution, but the overflow-visible on the modal itself may cause other content to escape the modal boundary.

Suggestion: Check if the portal alone is sufficient and remove the overflow-visible override, or scope it more narrowly.

8. Submenu label uses raw Unicode arrow \u25B8

Line 853: The submenu item label is friendlyName(field?.name) + " ▸". The arrow character suggests this is a submenu indicator, but Nuxt UI's UContextMenu should render submenu arrows automatically for items with children.

Suggestion: Remove the manual arrow — let the component handle it.

9. No i18n — all menu labels are hardcoded English strings

Labels like 'Like this one', 'Full range', 'Zoom In', etc. are hardcoded. The blackbox-log-viewer may not have i18n yet, but worth noting for future migration.

10. Large function count with repetitive patterns

The setMinMax* and setMinMaxSelected* functions are near-duplicates — the "selected" variants operate on currentState.field while the "all" variants loop over currentState.graph.fields. This could be consolidated:

function applyToFields(fields, fn) {
  for (const field of fields) fn(field);
  emitUpdate();
}

Then each action becomes a one-liner calling applyToFields with either [currentState.field] or currentState.graph.fields.


Minor Nits

  • 19 commits for a single feature — could be squashed before merge
  • The emitUpdate() is called inside zoom handlers after e.preventDefault() to keep the menu open, but the same pattern isn't applied to other actions — inconsistent UX

Verdict

The feature is useful, but the implementation needs cleanup around reactivity (issues 1, 3), code duplication (issues 2, 10), and style (issues 5, 6). I'd suggest requesting changes before merge.

@haslinghuis

Copy link
Copy Markdown
Member

@demvlad review is AI based - it got wrong about i18n 😃

@sonarqubecloud

Copy link
Copy Markdown

@github-actions

Copy link
Copy Markdown

Preview URL: https://pr918.betaflight-blackbox.pages.dev

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Caution

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

⚠️ Outside diff range comments (1)
src/components/GraphConfigDialog.vue (1)

635-745: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Treat non-finite MinMax values as invalid in the bulk helpers.

Lines 637, 655, 675, 703, 716, 729, and 741 only exclude undefined. Existing configs are still cloned verbatim into field.curve.MinMax, so persisted NaN/Infinity values will pass these guards, poison the Math.max/Math.min math, and make the selected actions silently no-op. Please switch these checks to finite-number guards or normalize once on load.

💡 Minimal hardening
+function hasFiniteMinMax(mm) {
+  return Number.isFinite(mm?.min) && Number.isFinite(mm?.max);
+}
+
 function setMinMaxLikeThis() {
   const mm = currentState.value.field?.curve?.MinMax;
-  if (currentState.value.graph?.fields && mm?.min !== undefined && mm?.max !== undefined) {
+  if (currentState.value.graph?.fields && hasFiniteMinMax(mm)) {
     const min = mm.min;
     const max = mm.max;
     for (const field of currentState.value.graph.fields) {
       setMin(field, min);
       setMax(field, max);
@@
     for (const field of currentState.value.graph.fields) {
       const mm = field?.curve?.MinMax;
-      if (mm?.min !== undefined && mm?.max !== undefined) {
+      if (hasFiniteMinMax(mm)) {
         max = Math.max(max, mm.max);
         min = Math.min(min, mm.min);
       }
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/GraphConfigDialog.vue` around lines 635 - 745, The bulk MinMax
helpers (setMinMaxLikeThis, setMinMaxOneScale, setMinMaxCentered,
setMinMaxSelectedCentered, setMinMaxZoom, setMinMaxSelectedZoom,
setMinMaxToFullRangeDuringAllTime, setMinMaxSelectedToFullRangeDuringAllTime)
only guard against undefined and therefore allow NaN/Infinity to poison
Math.min/Math.max; update each helper to treat only finite numeric min/max as
valid (use Number.isFinite or an equivalent finite-number check) before using
mm.min/mm.max, or alternatively normalize field.curve.MinMax to finite numbers
when loading configs so subsequent checks can remain simple. Ensure all
occurrences listed replace the mm?.min !== undefined && mm?.max !== undefined
checks with finite-number guards and/or normalization to prevent silent no-ops.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@src/components/GraphConfigDialog.vue`:
- Around line 635-745: The bulk MinMax helpers (setMinMaxLikeThis,
setMinMaxOneScale, setMinMaxCentered, setMinMaxSelectedCentered, setMinMaxZoom,
setMinMaxSelectedZoom, setMinMaxToFullRangeDuringAllTime,
setMinMaxSelectedToFullRangeDuringAllTime) only guard against undefined and
therefore allow NaN/Infinity to poison Math.min/Math.max; update each helper to
treat only finite numeric min/max as valid (use Number.isFinite or an equivalent
finite-number check) before using mm.min/mm.max, or alternatively normalize
field.curve.MinMax to finite numbers when loading configs so subsequent checks
can remain simple. Ensure all occurrences listed replace the mm?.min !==
undefined && mm?.max !== undefined checks with finite-number guards and/or
normalization to prevent silent no-ops.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 6fe08d9d-9d8c-4fb5-843e-1afd93cd307f

📥 Commits

Reviewing files that changed from the base of the PR and between d498935 and ae1dd76.

📒 Files selected for processing (1)
  • src/components/GraphConfigDialog.vue

@haslinghuis haslinghuis merged commit af5e516 into betaflight:master May 20, 2026
8 checks passed
@demvlad demvlad deleted the minmax_ctrl_menu branch May 21, 2026 02:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants