Skip to content

Commit 476b61d

Browse files
thephezclaude
andcommitted
fix(dashmint-lab): compact large prices on card chip to prevent overflow
Card price chip overflowed its column for values past ~7 digits. Render compact (K/M/B/T/Q with one decimal) past 10M, with the exact value in the title attribute. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 781ef54 commit 476b61d

4 files changed

Lines changed: 98 additions & 4 deletions

File tree

example-apps/dashmint-lab/src/components/CardTile.tsx

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,12 @@ import { useState, useRef, useEffect } from "react";
99
import type { Card } from "../dash/queries";
1010
import type { DashSdk } from "../dash/types";
1111
import { rarityOf } from "../lib/rarity";
12-
import { formatCredits, truncateId, truncateName } from "../lib/format";
12+
import {
13+
formatCredits,
14+
formatCreditsCompact,
15+
truncateId,
16+
truncateName,
17+
} from "../lib/format";
1318
import { useDpnsName } from "../hooks/useDpnsName";
1419
import { documentUrl } from "../lib/explorer";
1520
import { CardArt } from "./CardArt";
@@ -90,8 +95,11 @@ export function CardTile({
9095
<div className="flex items-center justify-between">
9196
<RarityTag rarity={rarity} />
9297
{hasPrice && (
93-
<span className="rounded-full border border-line-2 px-2 py-0.5 font-mono text-[11px] text-ink-2">
94-
{formatCredits(card.$price)} cr
98+
<span
99+
className="rounded-full border border-line-2 px-2 py-0.5 font-mono text-[11px] text-ink-2"
100+
title={`${formatCredits(card.$price)} credits`}
101+
>
102+
{formatCreditsCompact(card.$price)} cr
95103
</span>
96104
)}
97105
</div>

example-apps/dashmint-lab/src/lib/format.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,42 @@ export function formatCredits(price: number | bigint | undefined): string {
1919
const n = typeof price === "bigint" ? price : BigInt(Math.trunc(price));
2020
return n.toLocaleString();
2121
}
22+
23+
/**
24+
* Compact credit formatter for tight UI (card chips, badges).
25+
* Up to 9,999,999 renders with thousands separators; beyond that it
26+
* collapses to K / M / B / T / Q with one decimal of precision.
27+
*
28+
* Precondition: `price` is non-negative. Platform credit prices cannot be
29+
* negative, so this helper does not handle the sign — a large negative
30+
* value would skip the compact branch and render as a long string.
31+
*/
32+
export function formatCreditsCompact(
33+
price: number | bigint | undefined,
34+
): string {
35+
if (price === undefined || price === null) return "";
36+
const n = typeof price === "bigint" ? price : BigInt(Math.trunc(price));
37+
if (n < 10_000_000n) return n.toLocaleString();
38+
39+
const units: Array<[bigint, string]> = [
40+
[1_000_000_000_000_000n, "Q"],
41+
[1_000_000_000_000n, "T"],
42+
[1_000_000_000n, "B"],
43+
[1_000_000n, "M"],
44+
[1_000n, "K"],
45+
];
46+
for (const [scale, suffix] of units) {
47+
if (n >= scale) {
48+
// One decimal of precision via integer math, then trim a trailing ".0".
49+
const tenths = (n * 10n) / scale;
50+
const whole = tenths / 10n;
51+
const frac = tenths % 10n;
52+
const body =
53+
frac === 0n
54+
? whole.toLocaleString()
55+
: `${whole.toLocaleString()}.${frac}`;
56+
return `${body}${suffix}`;
57+
}
58+
}
59+
return n.toLocaleString();
60+
}

example-apps/dashmint-lab/test/CardTile.test.tsx

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,33 @@ describe("CardTile", () => {
186186
expect(ownerLink!.getAttribute("rel")).toContain("noreferrer");
187187
});
188188

189+
it("renders the price chip exactly for small values with the full amount in title", () => {
190+
render(
191+
<CardTile
192+
card={{ ...card, $price: 9_999_999n }}
193+
currentIdentityId={null}
194+
sdk={sdk}
195+
/>,
196+
);
197+
198+
const chip = screen.getByText("9,999,999 cr");
199+
expect(chip.getAttribute("title")).toBe("9,999,999 credits");
200+
});
201+
202+
it("renders the price chip in compact form past the threshold and keeps the exact value in title", () => {
203+
render(
204+
<CardTile
205+
card={{ ...card, $price: 55_555_555_555_555n }}
206+
currentIdentityId={null}
207+
sdk={sdk}
208+
/>,
209+
);
210+
211+
const chip = screen.getByText("55.5T cr");
212+
expect(chip.getAttribute("title")).toBe("55,555,555,555,555 credits");
213+
expect(screen.queryByText("55,555,555,555,555 cr")).toBeNull();
214+
});
215+
189216
it("copies the card id and opens the explorer from the overflow menu", () => {
190217
render(<CardTile card={card} currentIdentityId={null} sdk={sdk} />);
191218

example-apps/dashmint-lab/test/dash.test.ts

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { describe, expect, it } from "vitest";
22

3-
import { formatCredits } from "../src/lib/format";
3+
import { formatCredits, formatCreditsCompact } from "../src/lib/format";
44
import { normalizeCards } from "../src/dash/queries";
55
import { rarityOf } from "../src/lib/rarity";
66
import { withAuthedCard } from "../src/dash/withAuthedCard";
@@ -72,6 +72,26 @@ describe("dashmint helpers", () => {
7272
expect(rarityOf(3, 4)).toBe("common");
7373
});
7474

75+
it("formatCreditsCompact keeps small values exact and abbreviates large ones", () => {
76+
// Below the 10M threshold: full thousands separators, parity with formatCredits.
77+
expect(formatCreditsCompact(undefined)).toBe("");
78+
expect(formatCreditsCompact(0n)).toBe("0");
79+
expect(formatCreditsCompact(9_999_999n)).toBe("9,999,999");
80+
81+
// Threshold boundary flips into compact form.
82+
expect(formatCreditsCompact(10_000_000n)).toBe("10M");
83+
84+
// Each scale represented; trailing ".0" is trimmed when the scale is exact.
85+
expect(formatCreditsCompact(123_456_789n)).toBe("123.4M");
86+
expect(formatCreditsCompact(2_000_000_000n)).toBe("2B");
87+
expect(formatCreditsCompact(1_999_999_999n)).toBe("1.9B");
88+
expect(formatCreditsCompact(55_555_555_555_555n)).toBe("55.5T");
89+
expect(formatCreditsCompact(1_000_000_000_000_000n)).toBe("1Q");
90+
91+
// number input takes the same path.
92+
expect(formatCreditsCompact(15_000_000)).toBe("15M");
93+
});
94+
7595
it("withAuthedCard increments revision before invoking the mutation", async () => {
7696
const doc = { revision: 4n };
7797
const keyManager = {

0 commit comments

Comments
 (0)