import { type ClassValue, clsx } from "clsx" import { twMerge } from "tailwind-merge" export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs)) } /** Steps of a thousand, largest first. */ const SI_STEPS: [number, string][] = [ [1e9, "G"], [1e6, "M"], [1e3, "k"], [1e-3, "m"], [1e-6, "µ"], ] /** * A reading at `digits` significant digits, with an SI prefix where the plain * number would be long: 15000 → "15k", 0.0031 → "3.1m", 203.63 → "204". * * Between 0.01 and 1000 the plain number is the shortest thing to read, so * nothing is prefixed there — "0.4" beats "400m", and a value written with its * unit after it ("0.4 ms") stays honest. Past those bounds the prefix is what * keeps a reading in its column, and beyond giga or micro the exponent is. * * Where it applies: anything read at a glance and bounded by the width of a * tile, a table cell, an axis gutter or a legend — a rollup's own counts * included, since 179240 executions is a size, not a number anyone reads * digit by digit. What the user acts on stays unrounded: a payload, a form * field, the edge inspector's value, and the standing counts (nodes, flows * running, queue depth) that are small by nature and exact by meaning. */ export function si(value: number, digits = 3): string { if (!Number.isFinite(value)) return "--" // Rounded before the step is picked, so 999.7 reads as "1k", not "1000". const rounded = Number(value.toPrecision(digits)) const size = Math.abs(rounded) if (size === 0) return "0" if (size >= 0.01 && size < 1e3) return String(rounded) if (size >= 1e12 || size < 1e-6) return rounded.toExponential(1) const [factor, suffix] = SI_STEPS.find(([step]) => size >= step) ?? [1, ""] return `${Number((rounded / factor).toPrecision(digits))}${suffix}` } /** Floor, divisor and unit per step, largest first. */ const DUR_STEPS: [number, number, string][] = [ [60_000, 60_000, "min"], [1_000, 1_000, "s"], [0.01, 1, "ms"], [0, 1e-3, "µs"], ] /** * A duration in milliseconds at `digits` significant digits, carrying the unit * it picked: 1240 → "1.24 s", 90000 → "1.5 min", 203.63 → "204 ms". * * Unlike `si` the unit is not the caller's to write. Shortening the number in * front of a fixed " ms" gives "1.2k ms", which is a duration nobody reads — * so the step moves the unit instead, and the value is rounded before that * step is picked, so 999.7 reads "1 s" rather than "1000 ms". * * Below a millisecond it holds down to 0.01, which is `si`'s own plain-number * window transposed onto units. That floor is deliberate: without it a tile * polled every ten seconds flips between "400 µs" and "1.2 ms", and a reading * that changes unit on every poll reads as broken rather than as fast. */ export function dur(ms: number, digits = 3): string { if (!Number.isFinite(ms)) return "--" const rounded = Number(ms.toPrecision(digits)) const size = Math.abs(rounded) if (size === 0) return "0 ms" const [, factor, unit] = DUR_STEPS.find(([floor]) => size >= floor) ?? [ 0, 1, "ms", ] return `${Number((rounded / factor).toPrecision(digits))} ${unit}` }