Skip to content

fix: cast record ID to string to prevent JS precision loss with snowflake IDs#91

Merged
ManukMinasyan merged 1 commit into3.xfrom
fix/snowflake-id-precision-loss
Mar 6, 2026
Merged

fix: cast record ID to string to prevent JS precision loss with snowflake IDs#91
ManukMinasyan merged 1 commit into3.xfrom
fix/snowflake-id-precision-loss

Conversation

@ManukMinasyan
Copy link
Contributor

Summary

  • Cast $record->getKey() to string in formatBoardRecord() so @js() emits a JSON string instead of a number
  • Prevents JavaScript precision loss for large integer IDs (snowflakes) that exceed Number.MAX_SAFE_INTEGER (2^53 - 1)
  • Includes regression test

Fixes the root cause at the data layer rather than patching individual Blade template usages.

Closes #88

Test plan

  • New regression test SnowflakeIdPrecisionTest verifies formatBoardRecord returns ID as string
  • Full test suite passes (106 tests, 0 failures)

…ision loss

Large integer IDs (e.g. snowflakes like 420533451316027392) exceed
JavaScript's Number.MAX_SAFE_INTEGER (2^53 - 1). When passed through
@js() in Blade, they were encoded as JSON numbers, causing silent
precision loss in the browser.

Casting to string at the data layer ensures @js() emits a quoted
string, which JavaScript preserves exactly.

Closes #88
Copilot AI review requested due to automatic review settings March 6, 2026 11:11
@ManukMinasyan ManukMinasyan merged commit c445230 into 3.x Mar 6, 2026
8 checks passed
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR addresses JavaScript precision loss when large integer (snowflake) record IDs are passed from PHP to the frontend by ensuring board record IDs are emitted as JSON strings instead of numbers.

Changes:

  • Cast Model::getKey() to string in formatBoardRecord() to avoid JS Number precision truncation for large IDs.
  • Add a Pest regression test covering the expected string ID behavior.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
src/Concerns/HasBoardRecords.php Forces formatted board record id to be a string to prevent precision loss in JS.
tests/Feature/SnowflakeIdPrecisionTest.php Adds regression coverage asserting board record IDs are strings and demonstrates JSON encoding differences.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +19 to +25

$board = app(TestBoard::class)->getBoard();
$formatted = $board->formatBoardRecord($task);

// The ID must be a string so @js() emits a JSON string ("123") not a number (123)
// This prevents JavaScript precision loss for large IDs like snowflakes
expect($formatted['id'])->toBeString();
Copy link

Copilot AI Mar 6, 2026

Choose a reason for hiding this comment

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

The regression test is checking that formatBoardRecord() returns a string id, which will fail if the cast is removed. For clarity (since the bug only shows up above Number.MAX_SAFE_INTEGER), consider explicitly creating a record with a snowflake-like id (e.g. 420533451316027392) and asserting the returned id matches that exact string.

Suggested change
$board = app(TestBoard::class)->getBoard();
$formatted = $board->formatBoardRecord($task);
// The ID must be a string so @js() emits a JSON string ("123") not a number (123)
// This prevents JavaScript precision loss for large IDs like snowflakes
expect($formatted['id'])->toBeString();
// Use a snowflake-like ID above Number.MAX_SAFE_INTEGER to exercise precision edge case
$task->id = 420533451316027392;
$board = app(TestBoard::class)->getBoard();
$formatted = $board->formatBoardRecord($task);
// The ID must be a string so @js() emits a JSON string ("123") not a number (123)
// This prevents JavaScript precision loss for large IDs like snowflakes
expect($formatted['id'])->toBeString();
expect($formatted['id'])->toBe('420533451316027392');

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good catch. Applied both suggestions in follow-up commit — test now uses a snowflake-sized ID (420533451316027392) with exact string assertion.

Comment on lines +28 to +39
test('card blade renders recordKey as string in wire:click for large IDs', function () {
$task = Task::factory()->todo()->withPosition('65535.0000000000')->create();

// Simulate what @js() does: json_encode the record ID
// If ID is an integer, json_encode produces a number literal which JS truncates
$idAsInt = (int) $task->id;
$jsonFromInt = json_encode(['recordKey' => $idAsInt]);

// If ID is a string, json_encode produces a quoted string which JS preserves
$idAsString = (string) $task->id;
$jsonFromString = json_encode(['recordKey' => $idAsString]);

Copy link

Copilot AI Mar 6, 2026

Choose a reason for hiding this comment

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

This test is named as if it renders the Blade card, but it only checks json_encode() output; additionally $task, $jsonFromInt, and $jsonFromString are computed and never asserted. Consider either (a) actually rendering the relevant Blade/Livewire output and asserting the wire:click contains a quoted recordKey, or (b) removing the unused setup and renaming the test to reflect what it verifies.

Suggested change
test('card blade renders recordKey as string in wire:click for large IDs', function () {
$task = Task::factory()->todo()->withPosition('65535.0000000000')->create();
// Simulate what @js() does: json_encode the record ID
// If ID is an integer, json_encode produces a number literal which JS truncates
$idAsInt = (int) $task->id;
$jsonFromInt = json_encode(['recordKey' => $idAsInt]);
// If ID is a string, json_encode produces a quoted string which JS preserves
$idAsString = (string) $task->id;
$jsonFromString = json_encode(['recordKey' => $idAsString]);
test('json_encode emits quoted recordKey for large string IDs', function () {

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed — removed unused variables and renamed to json_encode emits quoted recordKey for large string IDs.

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