The "running" tile counted paused and invalid flows as running, so it read "5/5" beside "1 flow(s) cannot run". Its note is now additive rather than a precedence chain, so a quarantine no longer hides the invalid count. Adds dur() beside si(): a ms reading picks its own unit, so a slow run reads "1.24 s" instead of "1.2k ms". Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HTsT1isxUjw5gtkJk8WhuA
79 lines
3.1 KiB
TypeScript
79 lines
3.1 KiB
TypeScript
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}`
|
|
}
|