-
Notifications
You must be signed in to change notification settings - Fork 0
fix: preserve large integers as strings to prevent precision loss #19
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
JavaScript's Number.MAX_SAFE_INTEGER is 2^53-1 (9007199254740991). UnixNano timestamps exceed this, causing precision loss when converted to float64. This fix keeps large integers as strings, allowing the server-side GetStringOk to handle them correctly. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
ReviewApproved ✓ The fix correctly addresses JavaScript's floating-point precision limitation for large integers (>2^53-1). Code Quality
Minor ObservationThe condition No blocking issues found. The fix is targeted and solves the precision loss problem effectively. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
Updates the client-side value parsing to avoid precision loss when handling large integer strings (e.g., UnixNano IDs) by preserving them as strings instead of converting to JavaScript numbers.
Changes:
- Updated
parseValue()to only coerce numeric strings tonumberwhen doing so won’t lose integer precision. - Added a safe-integer-range guard (
Number.MAX_SAFE_INTEGER) for integer parsing.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
livetemplate-client.ts
Outdated
| // Try to parse as number, but only if it's safe (won't lose precision) | ||
| const num = parseFloat(value); | ||
| if (!isNaN(num) && value.trim() === num.toString()) { | ||
| // Check if the number is within JavaScript's safe integer range | ||
| // Large integers (like UnixNano timestamps) lose precision as float64 | ||
| if (Number.isInteger(num) && Math.abs(num) > Number.MAX_SAFE_INTEGER) { | ||
| // Keep as string to preserve precision | ||
| return value; |
Copilot
AI
Jan 25, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When the numeric string is outside MAX_SAFE_INTEGER, this returns the original value (which may include leading/trailing whitespace). Since the guard condition uses value.trim(), a value like " 176935... " would now be preserved (including spaces) and sent to the server, which is inconsistent with the number-parsing path and can break ID matching. Consider trimming once (e.g., const trimmed = value.trim()) and returning the trimmed string for this branch.
| // Try to parse as number, but only if it's safe (won't lose precision) | |
| const num = parseFloat(value); | |
| if (!isNaN(num) && value.trim() === num.toString()) { | |
| // Check if the number is within JavaScript's safe integer range | |
| // Large integers (like UnixNano timestamps) lose precision as float64 | |
| if (Number.isInteger(num) && Math.abs(num) > Number.MAX_SAFE_INTEGER) { | |
| // Keep as string to preserve precision | |
| return value; | |
| const trimmed = value.trim(); | |
| // Try to parse as number, but only if it's safe (won't lose precision) | |
| const num = parseFloat(trimmed); | |
| if (!isNaN(num) && trimmed === num.toString()) { | |
| // Check if the number is within JavaScript's safe integer range | |
| // Large integers (like UnixNano timestamps) lose precision as float64 | |
| if (Number.isInteger(num) && Math.abs(num) > Number.MAX_SAFE_INTEGER) { | |
| // Keep as string (trimmed) to preserve precision while matching numeric parsing | |
| return trimmed; |
livetemplate-client.ts
Outdated
| // Try to parse as number, but only if it's safe (won't lose precision) | ||
| const num = parseFloat(value); | ||
| if (!isNaN(num) && value.trim() === num.toString()) { | ||
| // Check if the number is within JavaScript's safe integer range | ||
| // Large integers (like UnixNano timestamps) lose precision as float64 | ||
| if (Number.isInteger(num) && Math.abs(num) > Number.MAX_SAFE_INTEGER) { | ||
| // Keep as string to preserve precision | ||
| return value; | ||
| } | ||
| return num; |
Copilot
AI
Jan 25, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This change introduces important new parsing behavior (integers > Number.MAX_SAFE_INTEGER remain strings) but there doesn’t appear to be a unit test covering it. Since Jest coverage is collected for livetemplate-client.ts, please add tests that assert: (1) values at/under MAX_SAFE_INTEGER parse to numbers, (2) values over it stay strings, and ideally (3) behavior with surrounding whitespace.
- Trim whitespace once at the start of parseValue for consistency - Add comprehensive unit tests for large integer handling Tests verify: - Values at/under MAX_SAFE_INTEGER parse to numbers - Values over MAX_SAFE_INTEGER stay as strings - Whitespace handling is consistent Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Code ReviewOverall: Good fix for the precision loss issue with large integers. The approach is sound and well-tested. Issues Found🔴 Critical Logic Bug (livetemplate-client.ts:526)
The Suggested fix: const num = parseFloat(trimmed);
if (!isNaN(num)) {
if (Number.isInteger(num) && Math.abs(num) > Number.MAX_SAFE_INTEGER) {
return trimmed;
}
if (trimmed === num.toString()) {
return num;
}
}Minor Issues
Positive Notes
|
The previous logic had a critical bug: the MAX_SAFE_INTEGER check was
inside a condition that already fails for large integers due to precision
loss during parseFloat(). For example:
- parseFloat("1769358878696557000") returns 1769358878696557056
- "1769358878696557000" === "1769358878696557056" is false
- So the outer if block never executed and MAX_SAFE_INTEGER was unreachable
The fix moves the range check BEFORE the string equality check, ensuring
large integers are correctly identified and preserved as strings.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Updates the embedded browser client to fix a critical bug where the MAX_SAFE_INTEGER check was unreachable for large integers due to precision loss during parseFloat(). See livetemplate/client#19 for details. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Review SummaryThe fix correctly addresses precision loss for large integers. The logic is sound and well-tested. Bug FoundBoolean parsing inconsistency (line 537-538): The boolean checks use the original Fix: if (trimmed === "true") return true;
if (trimmed === "false") return false;Code Quality
The fix solves a real data corruption issue. Just fix the boolean trimming inconsistency and this is ready to merge. |
* chore: update embedded client with large integer fix Updates the embedded client library to include the fix for preserving large integers (like UnixNano timestamps) as strings to prevent JavaScript precision loss. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * chore: update embedded client with whitespace fix Updates the embedded client to include the parseValue whitespace trimming fix from the code review. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * chore: update embedded client with MAX_SAFE_INTEGER logic fix Updates the embedded browser client to fix a critical bug where the MAX_SAFE_INTEGER check was unreachable for large integers due to precision loss during parseFloat(). See livetemplate/client#19 for details. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Summary
parseValue()to checkNumber.MAX_SAFE_INTEGERtime.Now().UnixNano()lost precision when converted to JavaScript numbersProblem
JavaScript's
Number.MAX_SAFE_INTEGERis 2^53-1 (9007199254740991). UnixNano timestamps exceed this limit, causing precision loss when the client'sparseValue()function converted them to numbers.For example:
17693588786965570001769358878696557056(different due to float64 precision loss!)Solution
The
parseValue()function now checks if an integer exceedsNumber.MAX_SAFE_INTEGERand keeps it as a string, allowing the server-sideGetStringOk()to handle it correctly.Test plan
🤖 Generated with Claude Code