Skip to content

fix(settings): allow negative coordinates in position config - #1381

Open
dzienisz wants to merge 2 commits into
meshtastic:mainfrom
dzienisz:fix/negative-position-coordinates
Open

fix(settings): allow negative coordinates in position config#1381
dzienisz wants to merge 2 commits into
meshtastic:mainfrom
dzienisz:fix/negative-position-coordinates

Conversation

@dzienisz

@dzienisz dzienisz commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Fixes #1308.

Number.parseFloat("-").toString() produced "NaN" while typing a negative latitude/longitude, preventing users in the southern/western hemisphere from entering coordinates. This change preserves intermediate values (-, empty string, trailing decimal point) and only normalizes complete numbers.

Also fixes the latitude/longitude field length limits so negative values with 7 decimal places fit (-34.1147648 / -180.0000000).

Adds Zod refine validation on latitude and longitude to enforce a maximum of 7 decimal places, matching the wire format precision (latitudeI/longitudeI are stored as degrees × 10⁷).

Adds unit tests for the position schema covering positive/negative coordinates, 7 decimal precision, and out-of-range values.

Generated with Devin

Summary by CodeRabbit

  • Bug Fixes
    • Improved numeric input handling by preserving temporary values such as empty fields, negative signs, and trailing decimals.
    • Prevented invalid numeric entries from being replaced with “NaN.”
    • Increased the maximum input lengths for latitude and longitude values.
    • Added validation to reject latitude and longitude values with more than seven decimal places while retaining range checks.

@vercel

vercel Bot commented Aug 3, 2026

Copy link
Copy Markdown

Someone is attempting to deploy a commit to the Meshtastic Team on Vercel.

A member of the Team first needs to authorize it.

@CLAassistant

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.


Kamil Dzieniszewski seems not to be a GitHub user. You need a GitHub account to be able to sign the CLA. If you have already a GitHub account, please add the email address used for this commit to your account.
You have signed the CLA already but the status is still pending? Let us recheck it.

@coderabbitai

coderabbitai Bot commented Aug 3, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Numeric input handling now preserves intermediate and invalid values. Position fields support longer coordinate values. Position validation limits optional latitude and longitude values to seven decimal places while retaining range checks.

Changes

Numeric position input handling

Layer / File(s) Summary
Preserve numeric input states
apps/web/src/components/Form/FormInput.tsx
FormInput preserves empty, negative, trailing-decimal, and invalid numeric text. Valid numeric input uses parseFloat.
Validate position values
apps/web/src/validation/config/position.ts, apps/web/src/components/PageComponents/Settings/Position.tsx, apps/web/src/validation/config/position.test.ts
Latitude and longitude retain range checks, allow at most seven decimal places, and support maximum lengths of 11 and 12 characters. Tests cover valid coordinates, excessive precision, and out-of-range values.

Estimated code review effort: 2 (Simple) | ~10 minutes

Poem

A rabbit types a minus sign bright,
The decimal stays in sight.
No “NaN” hops through the form,
Seven places keep the norm.
Longer coordinates fit just right.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly describes the primary fix for entering negative coordinates in position configuration.
Description check ✅ Passed The description explains the problem, linked issue, implementation changes, validation behavior, and added tests.
Linked Issues check ✅ Passed The changes directly address issue [#1308] by enabling negative coordinates and supporting the documented coordinate precision.
Out of Scope Changes check ✅ Passed All changes support negative coordinate entry, coordinate limits, precision validation, or related test coverage.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch

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.

…sion

Preserve intermediate input values (-, empty string, trailing decimal point)
in number fields instead of converting them to "NaN".

Raise the latitude/longitude fieldLength limits so negative values with
7 decimal places fit (-34.1147648 / -180.0000000).

Add Zod refine validation that enforces a maximum of 7 decimal places on
latitude and longitude, replacing the incorrect string-length-based check.

Fixes meshtastic#1308

Generated with [Devin](https://devin.ai)

@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

🤖 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 `@apps/web/src/components/Form/FormInput.tsx`:
- Around line 73-75: Update the onChange normalization around parsed in
FormInput so numeric validation covers the complete newValue rather than
accepting parseFloat prefixes. Preserve intermediate empty, negative, and
decimal-typing states, accept only finite complete numeric values (including
-34.1147648), and retain invalid text such as "1e", "12abc", "1.2.3", and
"Infinity"; add regression tests for the specified cases.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 3e792b89-18de-453a-9a77-c6a6f620759f

📥 Commits

Reviewing files that changed from the base of the PR and between 9a47442 and 2b016f0.

📒 Files selected for processing (2)
  • apps/web/src/components/Form/FormInput.tsx
  • apps/web/src/components/PageComponents/Settings/Position.tsx
💤 Files with no reviewable changes (1)
  • apps/web/src/components/PageComponents/Settings/Position.tsx

Comment on lines +73 to +75
const parsed = Number.parseFloat(newValue);
controllerField.onChange(
field.type === "number"
? Number.parseFloat(newValue).toString()
: newValue,
Number.isNaN(parsed) ? newValue : parsed.toString(),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

node - <<'NODE'
for (const value of ["1e", "1e-", "12abc", "1.2.3", "Infinity"]) {
  const parsed = Number.parseFloat(value);
  console.log(value, "=>", Number.isNaN(parsed) ? value : parsed.toString());
}
NODE

Repository: meshtastic/web

Length of output: 215


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate and inspect the target component around the reported lines.
printf '%s\n' '--- target file vicinity ---'
sed -n '1,120p' apps/web/src/components/Form/FormInput.tsx | cat -n

printf '%s\n' '--- focused relevant section ---'
sed -n '60,85p' apps/web/src/components/Form/FormInput.tsx | cat -n

printf '%s\n' '--- related tests/usages if present ---'
rg -n "FormInput|parseFloat|Number.isFinite|Number\\(" -S apps/web/src/components/Form apps/web/src 2>/dev/null | head -80 || true

Repository: meshtastic/web

Length of output: 6853


🏁 Script executed:

#!/bin/bash
set -euo pipefail

node - <<'NODE'
const cases = [
  "1e",
  "1e-",
  "12abc",
  "1.2.3",
  "Infinity",
  "Infinityabc",
  "-34.1147648",
  "-34.1147648abc",
  ".1147648",
  "+1.2.3",
  "-1.2.3",
  "",
  "-",
  ".",
  "-.",
  "-.",
];

console.log("parseFloat results:");
for (const value of cases) {
  const parsed = Number.parseFloat(value);
  const normalized = Number.isNaN(parsed) ? value : parsed.toString();
  console.log(JSON.stringify(value), "=>", JSON.stringify(parsed), "normalized =>", JSON.stringify(normalized));
}

console.log("\nNumber with full-string regex results:");
const floatRe = /^[+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?$/;
for (const value of cases) {
  const parsed = Number(value);
  const complete = Number.isFinite(parsed) && (value === "" || floatRe.test(value));
  console.log(
    JSON.stringify(value),
    "=>",
    JSON.stringify(parsed),
    "complete number =>",
    complete.toString().toLowerCase(),
    "normalized =>",
    JSON.stringify(complete ? parsed.toString() : value),
  );
}
NODE

Repository: meshtastic/web

Length of output: 1847


Validate the complete numeric string before normalizing.

Number.parseFloat(newValue) accepts numeric prefixes. "1e", "1e-", "12abc", and "1.2.3" are normalized to "1", "1", "12", and "1.2", so valid prefix text is lost. Valid Infinity text is also preserved through this path.

Use a Number.isFinite() parse result for valid strings and preserve intermediate empty, negative, or decimal-typing states separately. Add regression tests for "1e", "12abc", "Infinity", and -34.1147648.

Proposed fix
-    const parsed = Number.parseFloat(newValue);
+    const parsed = Number(newValue);
+    const isCompleteNumber =
+      Number.isFinite(parsed) &&
+      /^[+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?$/.test(newValue);
     controllerField.onChange(
-      Number.isNaN(parsed) ? newValue : parsed.toString(),
+      isCompleteNumber ? parsed.toString() : newValue,
     );
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const parsed = Number.parseFloat(newValue);
controllerField.onChange(
field.type === "number"
? Number.parseFloat(newValue).toString()
: newValue,
Number.isNaN(parsed) ? newValue : parsed.toString(),
const parsed = Number(newValue);
const isCompleteNumber =
Number.isFinite(parsed) &&
/^[+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?$/.test(newValue);
controllerField.onChange(
isCompleteNumber ? parsed.toString() : newValue,
🤖 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 `@apps/web/src/components/Form/FormInput.tsx` around lines 73 - 75, Update the
onChange normalization around parsed in FormInput so numeric validation covers
the complete newValue rather than accepting parseFloat prefixes. Preserve
intermediate empty, negative, and decimal-typing states, accept only finite
complete numeric values (including -34.1147648), and retain invalid text such as
"1e", "12abc", "1.2.3", and "Infinity"; add regression tests for the specified
cases.

@dzienisz
dzienisz force-pushed the fix/negative-position-coordinates branch from 2b016f0 to ce35dc3 Compare August 3, 2026 19:52

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

🤖 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 `@apps/web/src/validation/config/position.ts`:
- Around line 24-35: Update the latitude and longitude schemas in the position
validation definition to normalize blank string inputs to undefined before
numeric coercion, preserving optional behavior for cleared fields. Ensure
clearing either coordinate, or both coordinates, produces undefined rather than
numeric zero in the form output consumed by submit.
- Around line 6-10: Update maxDecimalPlaces to account for exponent digits when
validating decimal precision, rather than relying only on splitting
value.toString() at the decimal point. Ensure exponential values such as 1.2e-7
are rejected when their effective decimal places exceed places, while preserving
the existing undefined and valid-precision behavior.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: a9d656bb-d944-4eb8-80b7-8b93708f6c19

📥 Commits

Reviewing files that changed from the base of the PR and between 2b016f0 and ce35dc3.

📒 Files selected for processing (3)
  • apps/web/src/components/Form/FormInput.tsx
  • apps/web/src/components/PageComponents/Settings/Position.tsx
  • apps/web/src/validation/config/position.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/web/src/components/Form/FormInput.tsx

Comment on lines +6 to +10
const maxDecimalPlaces = (places: number) => (value: number | undefined) => {
if (value === undefined) return true;
const [, decimals] = value.toString().split(".");
return !decimals || decimals.length <= places;
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

node <<'NODE'
for (const value of [1e-7, 1.2e-7]) {
  const [, decimals] = value.toString().split(".");
  console.log({
    value,
    representation: value.toString(),
    currentResult: !decimals || decimals.length <= 7,
  });
}
NODE

Repository: meshtastic/web

Length of output: 278


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

echo "== locate position.ts =="
fd 'position\.ts$' . | sed -n '1,20p'

echo "== file overview =="
for f in $(fd 'position\.ts$' .); do
  echo "--- $f"
  wc -l "$f"
  sed -n '1,80p' "$f" | cat -n
done

echo "== search schema/usages =="
rg -n "maxDecimalPlaces|positionSchema|position" apps/web/src/validation apps/web/src -S | sed -n '1,200p'

echo "== small value string forms for DecimalPlaces =="
node <<'NODE'
for (const places of [6,7,8,9]) {
  for (const value of [1e-7, 1.2e-7, 1.2345678e-2]) {
    const [, decimals] = value.toString().split(".");
    console.log(JSON.stringify({ value, places, toString: value.toString(), decimalFraction: decimals, currentResult: !decimals || decimals.length <= places }));
  }
}
NODE

Repository: meshtastic/web

Length of output: 27657


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

echo "== inspect Zod/number coercion path if present =="
rg -n "preprocess|number\(\)|z\\.number|schema:|schema:" apps/web/src/validation apps/web/src/schema -S | sed -n '1,220p'

fd '.*\.ts$' apps/web/src/validation/apps/web/src/schema 2>/dev/null | xargs -r sed -n '1,180p' | sed -n '1,180p'

Repository: meshtastic/web

Length of output: 8156


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

echo "== validate.ts =="
sed -n '1,80p' apps/web/src/validation/validate.ts | cat -n

echo "== settings position component relevant fields =="
sed -n '220,255p' apps/web/src/components/PageComponents/Settings/Position.tsx | cat -n

Repository: meshtastic/web

Length of output: 2320


Count decimal places across exponential notation.

Number.prototype.toString() can produce exponential form for small coordinates. split(".") then misses the exponent, so values like 1.2e-7 pass as only one decimal place when they represent eight. Handle the exponent explicitly or validate the original string before coercion.

🤖 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 `@apps/web/src/validation/config/position.ts` around lines 6 - 10, Update
maxDecimalPlaces to account for exponent digits when validating decimal
precision, rather than relying only on splitting value.toString() at the decimal
point. Ensure exponential values such as 1.2e-7 are rejected when their
effective decimal places exceed places, while preserving the existing undefined
and valid-precision behavior.

Comment on lines +24 to +35
latitude: z.coerce
.number()
.min(-90)
.max(90)
.optional()
.refine(maxDecimalPlaces(7), { message: "Max 7 decimal precision" }),
longitude: z.coerce
.number()
.min(-180)
.max(180)
.optional()
.refine(maxDecimalPlaces(7), { message: "Max 7 decimal precision" }),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

node <<'NODE'
for (const raw of ["", " ", "-", "34."]) {
  console.log(JSON.stringify({ raw, coerced: Number(raw) }));
}
NODE

Repository: meshtastic/web

Length of output: 253


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

echo "Files:"
fd -a 'position\.ts|Position\.tsx' . | sed 's#^\./##'

echo
echo "position.ts outline:"
ast-grep outline apps/web/src/validation/config/position.ts || true

echo
echo "position.ts content:"
cat -n apps/web/src/validation/config/position.ts

echo
echo "Position.tsx relevant section:"
sed -n '120,185p' apps/web/src/components/PageComponents/Settings/Position.tsx | cat -n

echo
echo "Search position validation usages:"
rg -n "PositionValidationSchema|latitude|longitude" apps/web/src -g '*.ts' -g '*.tsx' | head -120

Repository: meshtastic/web

Length of output: 11787


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

echo "DynamicForm definition locations:"
rg -n "DynamicForm|type .*FormProps|componentProps|ReactHTML|submit" -g '*.ts' -g '*.tsx' apps/web/src | head -200

echo
echo "Search for DynamicForm implementation:"
rg -n "export .*DynamicForm|function DynamicForm|const DynamicForm" apps/web/src -g '*.ts' -g '*.tsx'

Repository: meshtastic/web

Length of output: 18021


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

echo "DynamicForm implementation:"
cat -n apps/web/src/components/Form/DynamicForm.tsx | sed -n '76,195p'

echo
echo "DynamicFormField implementation:"
cat -n apps/web/src/components/Form/DynamicFormField.tsx | sed -n '1,140p'

echo
echo "Position settings relevant input fields:"
cat -n apps/web/src/components/PageComponents/Settings/Position.tsx | sed -n '215,260p'

echo
echo "FormInput implementation:"
cat -n apps/web/src/components/Form/FormInput.tsx | sed -n '1,120p'

echo
echo "Root validation imports:"
cat -n apps/web/src/validation/config/position.ts | sed -n '1,60p'

echo
echo "Node coercion behavior for blank inputs:"
node - <<'NODE'
const inputs = ["", " ", "\t", "-"];
for (const value of inputs) {
  console.log(JSON.stringify({
    display: value || "<empty>",
    asNumberString: Number(String(value)),
    passedToNumberSchemaLike: Number(String(value)),
  }));
}
NODE

Repository: meshtastic/web

Length of output: 15398


Map empty coordinates to undefined before numeric coercion.

z.coerce.number() treats "" as 0 before .optional(), so clearing a latitude or longitude field can pass a defined zero coordinate to submit and later send a fixed position with that coordinate cleared to 0. Normalize blank strings to undefined before coercion, or update the form output contract and add tests for clearing one coordinate and both coordinates.

[low effort]

🤖 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 `@apps/web/src/validation/config/position.ts` around lines 24 - 35, Update the
latitude and longitude schemas in the position validation definition to
normalize blank string inputs to undefined before numeric coercion, preserving
optional behavior for cleared fields. Ensure clearing either coordinate, or both
coordinates, produces undefined rather than numeric zero in the form output
consumed by submit.

Covers positive/negative latitude and longitude with 7 decimal places,
rejection of values exceeding 7 decimal places, and rejection of values
outside the valid range.

Generated with [Devin](https://devin.ai)

@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

🤖 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 `@apps/web/src/validation/config/position.test.ts`:
- Around line 53-67: Extend the tests around PositionValidationSchema to cover
both sides of each coordinate bound: reject latitude -91 and longitude 181, and
verify the inclusive boundary values -90, 90, -180, and 180 are accepted using
validBase.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: dab35dee-e7ee-455d-8eac-b526957fd03f

📥 Commits

Reviewing files that changed from the base of the PR and between ce35dc3 and ff5e127.

📒 Files selected for processing (1)
  • apps/web/src/validation/config/position.test.ts

Comment on lines +53 to +67
it("rejects latitude outside the valid range", () => {
const result = PositionValidationSchema.safeParse({
...validBase,
latitude: 91,
});
expect(result.success).toBe(false);
});

it("rejects longitude outside the valid range", () => {
const result = PositionValidationSchema.safeParse({
...validBase,
longitude: -181,
});
expect(result.success).toBe(false);
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Test both ends of each coordinate range.

These cases test only latitude values above 90 and longitude values below -180. A schema that accepts latitude below -90 or longitude above 180 still passes this suite. Add rejection tests for -91 and 181. Add acceptance tests for -90, 90, -180, and 180 if the documented bounds are inclusive.

🤖 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 `@apps/web/src/validation/config/position.test.ts` around lines 53 - 67, Extend
the tests around PositionValidationSchema to cover both sides of each coordinate
bound: reject latitude -91 and longitude 181, and verify the inclusive boundary
values -90, 90, -180, and 180 are accepted using validBase.

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.

[Bug]: Cannot enter negative degrees on config position page

2 participants