fix: address review — ring buffer validation, div-by-zero, sparkline alignment

- Add ringBuffer length validation in extractSeries and extractSupplySeries
- Fix division-by-zero when oldest holder count is 0
- Align supply series start with other series (consistent pre-launch skip)
- Center flat sparklines vertically instead of pinning to bottom
This commit is contained in:
openhands 2026-02-22 19:39:36 +00:00
parent 66930d7013
commit bd5c661504

View file

@ -95,13 +95,19 @@ function weiToEth(wei: string | null | undefined): number {
* Skips leading zeros (pre-launch padding).
*/
function extractSeries(ringBuffer: string[], pointer: number, slotOffset: number): number[] {
if (ringBuffer.length !== RING_HOURS * RING_SEGMENTS) {
// eslint-disable-next-line no-console
console.warn(`Ring buffer size mismatch: expected ${RING_HOURS * RING_SEGMENTS}, got ${ringBuffer.length}`);
return [];
}
const raw: number[] = [];
for (let i = 0; i < RING_HOURS; i++) {
// Walk from oldest to newest
const idx = ((pointer + 1 + i) % RING_HOURS) * RING_SEGMENTS + slotOffset;
raw.push(Number(ringBuffer[idx] || '0'));
}
// Skip leading zeros
// Skip leading zeros (pre-launch padding) use findIndex on any non-zero
// Note: legitimate zero values mid-series are kept, only leading zeros trimmed
const firstNonZero = raw.findIndex(v => v > 0);
return firstNonZero === -1 ? [] : raw.slice(firstNonZero);
}
@ -110,6 +116,7 @@ function extractSeries(ringBuffer: string[], pointer: number, slotOffset: number
* Build cumulative net supply series from minted (slot 1) and burned (slot 2).
*/
function extractSupplySeries(ringBuffer: string[], pointer: number): number[] {
if (ringBuffer.length !== RING_HOURS * RING_SEGMENTS) return [];
const minted: number[] = [];
const burned: number[] = [];
for (let i = 0; i < RING_HOURS; i++) {
@ -117,15 +124,15 @@ function extractSupplySeries(ringBuffer: string[], pointer: number): number[] {
minted.push(Number(ringBuffer[idx + 1] || '0'));
burned.push(Number(ringBuffer[idx + 2] || '0'));
}
// Build cumulative net supply change
// Find first hour with any activity (align with extractSeries)
const firstActive = minted.findIndex((m, i) => m > 0 || burned[i] > 0);
if (firstActive === -1) return [];
// Build cumulative net supply change from first active hour
const cumulative: number[] = [];
let sum = 0;
let hasData = false;
for (let i = 0; i < RING_HOURS; i++) {
const net = minted[i] - burned[i];
if (net !== 0) hasData = true;
sum += net;
if (hasData) cumulative.push(sum);
for (let i = firstActive; i < RING_HOURS; i++) {
sum += minted[i] - burned[i];
cumulative.push(sum);
}
return cumulative;
}
@ -138,10 +145,11 @@ function toSvgPoints(series: number[]): string {
const min = Math.min(...series);
const max = Math.max(...series);
const range = max - min || 1;
const isFlat = max === min;
return series
.map((v, i) => {
const x = (i / (series.length - 1)) * 80;
const y = 24 - ((v - min) / range) * 22 - 1; // 1px padding top/bottom
const y = isFlat ? 12 : 24 - ((v - min) / range) * 22 - 1; // center flat lines
return `${x.toFixed(1)},${y.toFixed(1)}`;
})
.join(' ');
@ -169,7 +177,7 @@ const holderGrowthIndicator = computed((): string | null => {
if (series.length < 2) return null;
const oldest = series[0];
const newest = series[series.length - 1];
if (oldest === 0) return null;
if (oldest === 0) return newest > 0 ? `${newest} holders` : null;
const pct = ((newest - oldest) / oldest) * 100;
if (Math.abs(pct) < 0.1) return '~ flat';
return pct > 0 ? `${pct.toFixed(1)}% this week` : `${Math.abs(pct).toFixed(1)}% this week`;