Skip to content

fix(ui): sort the four token columns by their real value - #220

Merged
argszero merged 1 commit into
mainfrom
fix/transactions-token-column-sort
Sep 13, 2026
Merged

argszero merged 1 commit into
mainfrom
fix/transactions-token-column-sort

Conversation

@argszero

Copy link
Copy Markdown
Owner

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:

  1. The rows can't move. 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 never painted. / are emitted only by tableTheadHtml, 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 moment state.sort is 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 sortVal reading 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.

Related Issue

None — self-discovered defect; no issue exists and none was fabricated.

Changes

  • ui/js/app.js: the four token columns declare sortVal (exact numeric field), paintSortIndicators() refreshes the / marker after each rebuild, and sortArrow() / colTitle() become the shared single definition
  • ui/index.html: cache-bust app.js?v=20260914-220260914-3 (the only file whose content changed is app.js)
  • No configuration or data-structure change (no Config, no schema, no new i18n key)

Tests

  • cargo test223 passed / 0 failed (unchanged: no Rust file is touched)
  • cargo fmt --check — exit 0
  • cargo clippy --all-targets — clean
  • New/updated unit test: N/A — the change is in ui/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.html plus the four real scripts (api.js, data.js, i18n.js, app.js) are booted in jsdom 30.0.1 with only fetch stubbed. The fixture is three consume rows (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".

  • Before: 8 red / 5 greentokens asc + desc inert, input / cached / output asc inert, and the marker absent for tokens and for pts.
  • After: 15 green / 0 failed, including a third click that clears the marker and restores the server order.
  • In-page controls prove the instrument itself works: pts asc + desc reorder, time reorders 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).
  • Two extra checks cover the marker's lifecycle: a third click on the header removes the sort and clears /.

Acceptance: the pre-change tree is rejected (A/B mutation matrix)

The same probe is re-run with C2102_APP pointing at a variant copy of app.js:

leg mutation red set
ORIG the pre-change tree (origin/main) 8 — the frozen set
M1 the four sortVal removed 5 — the four "reorders" checks + tokens desc
M2 the paintSortIndicators() call removed 3pts ▲, pts ▼, tokens
M3 sortArrow() always returns " ▲" (marker never cleared) 2
FIX the working tree 0 (positive control)

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

  • Branch naming follows convention (fix/)
  • Commit message uses Conventional Commits (fix(ui): …)
  • Single responsibility, minimal change (2 files: +42 / −9)
  • Non-money: display/ordering only — the ledger, balances and settlement are untouched

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.
@argszero
argszero merged commit 1d5fcdd into main Sep 13, 2026
1 check passed
@argszero
argszero deleted the fix/transactions-token-column-sort branch September 13, 2026 20:16
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.

1 participant