Frontmatter
| id | 9311 |
| title | Bug / Enhancement: Fix unstable sorting & enforce nulls at bottom |
| state | Closed |
| labels | bugenhancementaicore |
| assignees | tobiu |
| createdAt | Feb 26, 2026, 4:41 PM |
| updatedAt | Feb 26, 2026, 4:47 PM |
| githubUrl | https://github.com/neomjs/neo/issues/9311 |
| author | tobiu |
| commentsCount | 1 |
| parentIssue | null |
| subIssues | [] |
| subIssuesCompleted | 0 |
| subIssuesTotal | 0 |
| blockedBy | [] |
| blocking | [] |
| closedAt | Feb 26, 2026, 4:47 PM |
Bug / Enhancement: Fix unstable sorting & enforce nulls at bottom
tobiu assigned to @tobiu on Feb 26, 2026, 4:47 PM

tobiu
Feb 26, 2026, 4:47 PM
Input from Gemini 3.1 Pro:
✦ Successfully fixed and pushed to the
devbranch in commit dc446b439.Summary of Completion:
src/data/Store.mjsupdated to correctly applydefaultValueduring soft hydrationresolveField().src/collection/Base.mjsandsrc/collection/Sorter.mjsupdated to strictly isolatenullandundefinedvalues and force them to the bottom of the collection during sort operations (bothASCandDESC), fixing the transitivity issue.- Added the
test/playwright/unit/collection/SortNull.spec.mjsunit test to prevent regression.
tobiu closed this issue on Feb 26, 2026, 4:47 PM
Sorting columns with mapped data and missing values (like
companyin DevIndex) resulted in unstable, varying sort orders that seemingly "improved" the more times a sort was triggered.This occurred due to two overlapping issues:
Neo.data.Store#resolveFieldwas returningundefinedfor missing mapped keys on raw objects, while fully hydrated records correctly received theirdefaultValue(e.g.,null).Neo.collection.BaseandNeo.collection.Sorterrelied on standard<and>operators. In JS,nullandundefinedevaluate as "equal" to strings (both<and>returnfalse), breaking sorting transitivity and causing random scrambling of the array.This ticket fixes the bug by introducing an enhancement to the framework's sorting paradigm:
nullandundefinedvalues are now always pushed to the bottom of the collection, regardless of whether the sort direction isASCorDESC.Changes:
src/data/Store.mjs: UpdatedresolveField()to applydefaultValueif the raw value isundefined.src/collection/Base.mjs: Added explicit== nullisolation logic todoSort()to always pushnull/undefinedvalues to the bottom.src/collection/Sorter.mjs: Added explicit== nullisolation logic todefaultSortBy()to always pushnull/undefinedvalues to the bottom.test/playwright/unit/collection/SortNull.spec.mjs: Added a new unit test to explicitly verify this behavior.