Skip to content

fix(ui): spell one token quantity one way in the transactions view - #281

Merged
argszero merged 1 commit into
mainfrom
fix/tx-token-spelling-single-source
Sep 21, 2026
Merged

argszero merged 1 commit into
mainfrom
fix/tx-token-spelling-single-source

Conversation

@argszero

Copy link
Copy Markdown
Owner

Summary

One token quantity is spelled three ways in the transactions view, by three
different rules:

surface rule (before) 1500 reads
summary card (renderTxSummary) fmtM: Math.round(n / 1000) + "K" "2K"
table row (txsToView) fmtTokens: (n / 1000).toFixed(1) trimmed "1.5K"
CSV export (exportTxCsv) the cell's display string "1.5K"

Two consequences, both reachable with a filter that leaves one row:

  1. The card prints the number the row below it prints, spelled differently. The card
    is the aggregate of exactly those rows, so the divergence is visible in one screen:
    2K above, 1.5K below.
  2. The export writes an abbreviated view string into a data column. The screen has an
    exactness affordance — the cell tooltip (title="Total: 1500", the fmtTokensExact
    layer) — and the file has none: 1.5K is a string, not a summable number.

The K tier is the tier real data lives in (measured 2026-09 against the deployed database:
99.92% of the 47万 token values fall in the K tier, max(tokens) = 494 795, and the M tier
has zero rows) — so "the two rules agree in the M tier" is the only defence of the old
shape, and it is unreachable in practice.

The fix makes the spelling single-source:

  • the card delegates to fmtTokens (const fmtM = fmtTokens;) instead of carrying its
    own Math.round sibling implementation;
  • the export reads the raw numbers (<...>Raw in the view model) and converts with
    fmtTokensExact — the same helper the cell tooltip already used.

Related Issue

None — the repository has no open issue for this; it was found during a UI audit of the
transactions view. No Closes #N.

Changes

  • ui/js/app.js — 2 lines: const fmtM = fmtTokens; (the card delegates) and the four
    export columns now read fmtTokensExact(t.<...>Raw).
  • ui/index.html — cache-bust bump for app.js (20260922-3 → 20260922-4).
  • src/state_gate.rs — new gate
    the_transactions_view_states_one_token_quantity_one_way plus three self-proofs
    (the_r158_roster_is_real, the_r158_scanners_have_teeth,
    the_r158_rules_separate_the_variants). Test-only module: #[cfg(test)] mod state_gate, nothing compiles into release artifacts.
  • ui/README.md — documents the single-source rule and the gate's scope.
  • No configuration / data-structure change, so no sample file is involved.

Tests

  • cargo test — 326 passed / 0 failed (baseline main 322; +4 = the new tests).
  • cargo fmt --check — clean.
  • cargo clippy --all-targets -- -D warnings — clean (CI's exact command).
  • New unit tests added.

What the gate pins (shape)

Four rules, each with its own tooth, on a reader that classifies the source into
per-line verdicts:

  1. R1 — the summary card carries no token-spelling literal of its own.
  2. R2 — the summary card delegates (const fmtM = fmtTokens;).
  3. R3 — all four export columns read <...>Raw and go through the tooltip's helper
    (none missed).
  4. R4 (reverse) — the four table cells still print the abbreviated field, which blocks
    the tempting over-correction of making the file right by degrading the screen.

Three self-proofs keep the gate honest: the_r158_roster_is_real (renaming a view-model
field turns it red, i.e. the roster is read out of the code and not hard-coded),
the_r158_scanners_have_teeth (the extractors are exercised on synthetic input), and
the_r158_rules_separate_the_variants (five variants, each mutation flipping only the
rule it targets).

Evidence beyond the gate

The gate is lexical: it proves the card delegates to fmtTokens, and it does not
prove that fmtTokens numerically equals the exact value the tooltip names. That half was
covered by driving the real app (real index.html + the four real scripts, stubbed fetch)
in jsdom, 6 variants × 12 legs = 72 legs, each leg printing its own declaration next to its
verdict:

  • landed — every leg green;
  • revert (the whole fix undone in memory) — the five axis legs red: card 2K vs row
    1.5K, all four export token columns non-integer (["1.5K","1K","2.50M","1500"]);
  • m_partial_csv (three of four columns converted) — exactly the "all four" leg red;
  • m_plain_int_card (fmtM = String(Math.round(n))) — card 1500 vs row 1.5K, and the
    cached/M-tier agreement legs red;
  • m_cosmetic_screen (cells print the exact value instead) — R4's territory: the screen
    precondition red;
  • m_value_equal_clone — a re-implemented fmtM whose values equal fmtTokens's: green
    by declaration, not by accident — that mutation is exactly what a lexical check can
    see and a DOM check cannot, which is the division of labour stated in the README.

The gate fragment was also compiled and executed against a materialised baseline
(git archive of the pre-fix commit): the base leg fails only on the axis test
(35/36) and the patched leg is 36/36 green.

Checklist

  • Branch name follows the convention (fix/…).
  • Commit message uses Conventional Commits.
  • Single responsibility, minimal diff (2 production lines + 1 cache-bust + tests/README).

)

The summary card, the table row and the CSV export each carried their own
rule for the same number:

  card   fmtM        Math.round(n / 1000) + "K"      1500 -> "2K"
  row    fmtTokens   (n / 1000).toFixed(1) trimmed   1500 -> "1.5K"
  export the cell's *display string*                 1500 -> "1.5K"

With one row in the result the card prints exactly the number the row below
prints, spelled differently; and the export writes an abbreviated view string
into a data column, where the affordance the screen has (the cell tooltip) does
not exist at all. The K tier is the tier the deployment lives in (99.92% of
tokens, max 494 795), so "the two rules agree in the M tier" is unreachable
in practice.

- the card now delegates to `fmtTokens` (`const fmtM = fmtTokens;`)
- the export reads the raw numbers and converts with `fmtTokensExact`, the same
  helper the cell tooltip already used

New gate `state_gate::the_transactions_view_states_one_token_quantity_one_way`
(R1 the card carries no spelling literal of its own; R2 the card delegates;
R3 all four export columns read <...>Raw through the tooltip's helper; R4 the
reverse: the cells still print the abbreviated field), plus three self-proofs
(`the_r158_roster_is_real`, `the_r158_scanners_have_teeth`,
`the_r158_rules_separate_the_variants`). No i18n keys added or removed; no
production Rust touched.
@argszero

Copy link
Copy Markdown
Owner Author

Self-review (own-approve is not available to this account on its own PRs, so the review is recorded here).

Reviewed the diff against the run I actually performed.

  • Landed bytes: ui/js/app.js 60603c39c66ab71efb5f36817da28429 (204 781 chars),
    src/state_gate.rs 8b602227b2cc80b56a6ce1e1c2fabe8e, ui/README.md
    bceabc8eef3e8d14a6ac8be2f9fbd819, ui/index.html ec3a0668c0cc8b207038faf235ab3bb3.
  • The compile gate's patched app.js md5 is 60603c39c66ab71efb5f36817da28429 — i.e. the
    gate that ran (35/36 on the base leg, 36/36 on the patched leg) read exactly the bytes
    in this PR, not a paraphrase of them.
  • cargo test 326/0 · cargo fmt --check rc=0 · cargo clippy --all-targets -- -D warnings
    rc=0, all on this tree.

One thing changed after the first green run, and it is worth naming because it is the trap
this repository's CI sets: the fragment initially carried an all() method on the reading
struct that no test called. cargo test is happy with dead code; CI's
clippy --all-targets -- -D warnings is not. It was removed, and the durable fragment was
edited in step so the two stay the same text.

Scope, stated as scope rather than as silence: the gate is lexical — it pins that the card
delegates to fmtTokens, not that fmtTokens numerically equals the exact value the tooltip
names. The second half is carried by the jsdom run described in the PR body, and the mutation
that a lexical check can see but a DOM check cannot (m_value_equal_clone) is declared green
on purpose in that instrument.

@argszero
argszero merged commit c448d31 into main Sep 21, 2026
1 check passed
@argszero
argszero deleted the fix/tx-token-spelling-single-source branch September 21, 2026 22:45
@argszero argszero mentioned this pull request Sep 24, 2026
10 tasks
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