fix(ui): sort the four token columns by their real value - #220
Merged
Merged
Conversation
The transactions table's four Token columns (input / cached / output /
total) rendered a sortable header button, but clicking it never
reordered a row — while the Points and Time columns beside them sorted
correctly.
Two faces, one blind spot:
1. The comparator falls back to `row[key]`, and the view rows built by
`txsToView` do not carry those keys: the exact numbers live under
`inputRaw` / `cachedRaw` / `outputRaw` / `tokensRaw`, while `tokens`
and the three `*Tokens` fields hold the K/M *display* strings
("5K"). So `Number(undefined)` and `Number("5K")` are NaN, `cmp` is
NaN, `cmp !== 0` is false and the comparator degenerates to
"equal" — the rows never move and nothing is reported.
2. The `▲`/`▼` direction marker is emitted only by `tableTheadHtml`,
which since #148 runs only when the container has no `<table>`
(that is what keeps the filter inputs alive). At that moment
`state.sort` is still empty, so the sort state was never shown for
any column — including the Points column, which does sort.
The fix applies the rule C2054 already established for the Points
column (sort on the value the cell shows) to the class: each token
column declares a `sortVal` that reads its exact numeric field, and the
direction marker is painted in place on every rebuild instead of
relying on the one-shot thead build. Rebuilding the thead was
deliberately avoided — it would destroy the filter inputs and break
#148's focus invariant. `sortArrow()` and `colTitle()` are now the
single definition shared by the thead builder, the in-place painter and
`tableBodyHtml`'s `data-label`.
One file (ui/js/app.js), zero Rust, zero new i18n keys, no Config or
schema change.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
The transactions table's four Token columns (input / cached / output / total) showed a sortable header button, but clicking it never reordered a row — while the Points and Time columns beside them sorted correctly.
Two faces, one blind spot:
row[key], and the view rows built bytxsToViewdo not carry those keys: the exact numbers live underinputRaw/cachedRaw/outputRaw/tokensRaw, whiletokensand the three*Tokensfields hold the K/M display strings ("5K"). SoNumber(undefined)andNumber("5K")are NaN,cmpis NaN,cmp !== 0is false, and the comparator degenerates to "equal" — the rows never move and nothing is reported.▲/▼are emitted only bytableTheadHtml, which since fix(ui): keep filter row alive during table rebuild so typing never loses focus (rant 2026-08-25T11:15:16) #148 runs only when the container has no<table>(that is what keeps the filter inputs alive). At that momentstate.sortis still empty, so the sort state was never visible for any column — including the Points column, which does sort.The fix applies the rule C2054 already established for the Points column (sort on the value the cell shows) to the class: each token column declares a
sortValreading its exact numeric field, and the direction marker is painted in place on every rebuild instead of relying on the one-shot thead build. Rebuilding the thead was deliberately avoided — it would destroy the filter inputs and break #148's focus invariant.sortArrow()andcolTitle()are now the single definition shared by the thead builder, the in-place painter andtableBodyHtml'sdata-label.Related Issue
None — self-discovered defect; no issue exists and none was fabricated.
Changes
ui/js/app.js: the four token columns declaresortVal(exact numeric field),paintSortIndicators()refreshes the▲/▼marker after each rebuild, andsortArrow()/colTitle()become the shared single definitionui/index.html: cache-bustapp.js?v=20260914-2→20260914-3(the only file whose content changed isapp.js)Tests
cargo test— 223 passed / 0 failed (unchanged: no Rust file is touched)cargo fmt --check— exit 0cargo clippy --all-targets— cleanui/js/app.js, and this repository has no JS test runner; the evidence is an end-to-end jsdom probe instead (below)Evidence: end-to-end probe (real DOM, not a re-implementation)
ui/index.htmlplus the four real scripts (api.js,data.js,i18n.js,app.js) are booted in jsdom 30.0.1 with onlyfetchstubbed. The fixture is threeconsumerows (tokens 5000 / 2000 / 300000) whose every expected order differs from the server order, so "rows did not move" is distinguishable from "rows moved to the same order".tokensasc + desc inert,input/cached/outputasc inert, and the marker absent fortokensand forpts.ptsasc + desc reorder,timereorders chronologically, and the filter input keeps focus across the debounced rebuild (fix(ui): keep filter row alive during table rebuild so typing never loses focus (rant 2026-08-25T11:15:16) #148's invariant).▲/▼.Acceptance: the pre-change tree is rejected (A/B mutation matrix)
The same probe is re-run with
C2102_APPpointing at a variant copy ofapp.js:origin/main)sortValremovedtokensdescpaintSortIndicators()call removedpts▲,pts▼,tokens▲sortArrow()always returns" ▲"(marker never cleared)M1 ∩ M2 = ∅ and M1 ∪ M2 = ORIG exactly, so every red is attributable to exactly one mechanism and the pre-change tree is rejected. M3 sits on top of the fix, so its red set cannot be disjoint from ORIG by construction — it is recorded as such and exists only to prove the new "third click clears the marker" assertion is not vacuous.
Checklist
fix/)fix(ui): …)