feat(aegis): 0.10.0 — shorten the balance by default, full precision on demand

Eight decimals of tail sat exactly where the eye lands. The hero now shows
two decimals once there is a whole part, because the whole units already
carry the significant digits, and digs past leading zeros otherwise so
amounts under 1 keep roughly four significant figures:

  1204.23234145 -> 1,204.23      0.23234145 -> 0.2323
  0.00012345    -> 0.0001234     0.00000001 -> 0.00000001

A flat "two decimal places" rule is the obvious version of this and it is
wrong: it renders 0.00042 BCH as 0.00 and the wallet reads as empty. The
rule here never collapses a non-zero balance to zeros, and it truncates
rather than rounds, so the figure shown is never more than the wallet holds
and sending the displayed amount always clears. An ellipsis marks the cut,
hover gives the exact figure, clicking pins full precision and is
remembered. Only this hero abbreviates — Send, MAX, history and the wallet
strip still call fmtBig, so every number acted on is exact.

Also repairs the regexes in the same function, which lost their backslashes
when the earlier commit was scripted: /^(-?)(\d+)(\.\d+)?$/ had become
/^(-?)(d+)(.d+)?$/, matching the letter "d". Still valid JS, so it parsed
cleanly and every balance quietly fell through to unformatted text — the
grouping and the dimmed decimals introduced in bb639a9 had never once run.
Caught by testing the function body extracted from the file rather than a
transcription of it.
This commit is contained in:
Local Dev 2026-09-27 10:17:15 +02:00
parent 570e9e6213
commit 18f2839bd4
2 changed files with 73 additions and 11 deletions

View file

@ -41,6 +41,11 @@
.bal .big { font-size: 32px; font-weight: 680; letter-spacing: -.6px; line-height: 1.1;
display: flex; align-items: baseline; gap: 5px; flex-wrap: wrap; }
.bal .big .dec { color: var(--dim); font-weight: 620; }
/* Marks a shortened tail, and only appears when one was actually cut. */
.bal .big .more { opacity: .5; margin-left: 1px; }
/* Pointer only when there is something to toggle — a balance that already
fits shows no affordance it cannot honour. */
.bal .big #balMain.balx { cursor: pointer; }
.bal .big small { font-size: 13px; color: var(--mut); font-weight: 500; margin-left: 0; }
/* Scoped to the hero — .fiat is also the Send form's conversion line. */
.bal .big .fiat { font-size: 14px; margin-left: 6px; }

View file

@ -140,22 +140,79 @@ function fmtBig(units, dec) {
const s = (Number(units || 0) / Math.pow(10, d)).toFixed(d);
return s.replace(/(\.\d*?[1-9])0+$|\.0+$/, "$1");
}
// Paint the header balance. Groups the integer part with thousands
// separators and wraps the fraction in .dec so CSS can dim it — the
// "$98,230.02" treatment the reference wallets lead with. Anything that is
// not a bare number (an em dash, a skeleton, an error) falls through to
// plain text. The regex guarantees the interpolated pieces are digits,
// sign and dot only, so building this with innerHTML cannot inject markup.
// Header balance. Groups the integer part and dims the fraction — the
// "98,230.02" treatment the reference wallets lead with — and by default
// shortens a long tail, because eight decimals of noise sit exactly where
// the eye lands.
//
// The shortening NEVER rounds and never collapses a non-zero balance to
// zeros, which is the way this idea normally goes wrong: a flat "2 decimal
// places" rule renders 0.00042 BCH as "0.00" and the wallet reads as empty.
// Instead it keeps two decimals minimum and then extends past any leading
// zeros so roughly four significant digits always survive, capped at what
// the amount actually has:
//
// 1204.23234145 -> 1,204.23 0.23234145 -> 0.2323
// 0.00012345 -> 0.0001234 0.00000001 -> 0.00000001 (1 sat, intact)
//
// It truncates rather than rounds, so the figure shown is never more than
// the wallet holds and sending the displayed amount always clears.
// An ellipsis marks the cut, hover shows the exact figure, and clicking
// pins full precision (remembered). Only this hero is abbreviated: Send,
// MAX, history and the wallet strip keep calling fmtBig directly, so every
// number you act on is exact.
const BAL_MIN_DP = 2, BAL_SIG_DP = 4;
let balFullText = "—";
let balFullPrecision = false;
try { balFullPrecision = localStorage.getItem("aegis/fullPrecision") === "1"; } catch (_e) {}
function setBalMain(text) {
balFullText = String(text == null ? "—" : text);
paintBalMain();
}
function paintBalMain() {
const el = $("balMain");
if (!el) return;
const s = String(text == null ? "—" : text);
const m = /^(-?)(d+)(.d+)?$/.exec(s);
if (!m) { el.textContent = s; return; }
const grouped = m[2].replace(/B(?=(d{3})+(?!d))/g, ",");
el.innerHTML = m[1] + grouped + (m[3] ? '<span class="dec">' + m[3] + "</span>" : "");
const m = /^(-?)(\d+)(\.\d+)?$/.exec(balFullText);
if (!m) {
el.textContent = balFullText;
el.removeAttribute("title");
el.classList.remove("balx");
return;
}
const group = (d) => d.replace(/\B(?=(\d{3})+(?!\d))/g, ",");
const head = m[1] + group(m[2]);
const fullFrac = m[3] ? m[3].slice(1) : "";
let frac = fullFrac, cut = false;
if (!balFullPrecision && fullFrac) {
// Whole units already carry the significant digits, so anything with a
// non-zero integer part only needs the two decimals. It is the amounts
// under 1 that have to dig past their leading zeros to keep any meaning.
const whole = m[2] !== "0";
const zeros = (/^0*/.exec(fullFrac) || [""])[0].length;
const want = whole ? BAL_MIN_DP : zeros + BAL_SIG_DP;
const keep = Math.max(BAL_MIN_DP, Math.min(fullFrac.length, want));
if (keep < fullFrac.length) { frac = fullFrac.slice(0, keep); cut = true; }
}
// Every interpolated piece is digits, a sign, a dot or a comma the regex
// vouched for, so innerHTML here cannot inject markup.
el.innerHTML = head + (frac
? '<span class="dec">.' + frac + (cut ? '<span class="more">' + String.fromCharCode(0x2026) + "</span>" : "") + "</span>"
: "");
const exact = head + (fullFrac ? "." + fullFrac : "");
const toggleable = cut || balFullPrecision;
el.title = cut ? exact + " — click for every decimal"
: (balFullPrecision ? exact + " — click to shorten" : exact);
el.classList.toggle("balx", toggleable);
}
$("balMain") && $("balMain").addEventListener("click", () => {
balFullPrecision = !balFullPrecision;
try { localStorage.setItem("aegis/fullPrecision", balFullPrecision ? "1" : "0"); } catch (_e) {}
paintBalMain();
});
function fmtSmall(units) {
if (typeof units === "string" && /^-?\d+$/.test(units)) return units.replace(/\B(?=(\d{3})+(?!\d))/g, ",");
return Number(units || 0).toLocaleString("en-US");