at main
4 folders
15 files
fix(cpu-page): correct the price history axis, break gaps, add average
The 90-day chart passed new Date(day).getTime() — milliseconds — to a
component that multiplies by 1000, putting every point ~56 million years
out. The plot shape survived because a constant factor on a self-scaling
axis is invisible, but the tick labels were month/day pairs from dates
centuries apart, which is why they read as running backwards.
Pass epoch seconds, align onto a daily grid so a day with no listing
breaks the line instead of being joined by a segment that implies a
price, and draw the same faded-raw + 7-day-average pair the statistics
page uses.
Chasing it surfaced a robustness hole in buildDayGrid: SQLite's
date(seen) is null for an unparseable timestamp, new Date(null) is epoch
0, and Math.min then stretched the grid back to 1970 — 20,673 points
with the real data one pixel wide. Non-finite and non-positive days are
now dropped before taking the extent, so one bad row costs one point
rather than the chart.
fix(cpu-page): correct the price history axis, break gaps, add average
The 90-day chart passed new Date(day).getTime() — milliseconds — to a
component that multiplies by 1000, putting every point ~56 million years
out. The plot shape survived because a constant factor on a self-scaling
axis is invisible, but the tick labels were month/day pairs from dates
centuries apart, which is why they read as running backwards.
Pass epoch seconds, align onto a daily grid so a day with no listing
breaks the line instead of being joined by a segment that implies a
price, and draw the same faded-raw + 7-day-average pair the statistics
page uses.
Chasing it surfaced a robustness hole in buildDayGrid: SQLite's
date(seen) is null for an unparseable timestamp, new Date(null) is epoch
0, and Math.min then stretched the grid back to 1970 — 20,673 points
with the real data one pixel wide. Non-finite and non-positive days are
now dropped before taking the extent, so one bad row costs one point
rather than the chart.
refactor(filter-spec): make the shared package authoritative for disk geometry
packages/filter-spec/src/constants.ts documents itself as the fix for constants
that 'were previously re-declared in each place that needed them' and ends
'Import from here instead.' Three files did; six did not, including the two
matchers the package was built for and the ServerFilter.svelte sliders its own
docstring names.
Now imported rather than redeclared:
- ServerFilter.svelte: dropped its six local disk constants and the three
inlined copies of diskSizeCeiling(); DISK_UNIT_GB replaces the hardcoded 500
in the six getFormattedDiskSize calls.
- api/shared/filter-query.ts: 12 '* 500' -> DISK_UNIT_GB, local cityPrefixes ->
CITY_PREFIXES.
- worker/alert-matching-sql.ts: 24 inline 500s -> ${DISK_UNIT_GB}, and the
hardcoded IN ('FSN','NBG','HEL') is now built from CITY_PREFIXES.
- api/frontend/stats.ts: the prefix CASE and WHERE are generated from
CITY_PREFIXES instead of three hand-written SQL literals.
- lib/filter.ts: the Hetzner URL builder rounds by DISK_UNIT_GB.
Both 'deliberately dependency-free' headers are corrected: they were justifying
the bypass, when the package resolves from both toolchains — which is its whole
purpose.
auction-data-transformer's NBG/FSN check is deliberately left alone: it maps
datacenter to country, so coupling it to CITY_PREFIXES would break the moment a
city is added in a new country.
Also removes the write-only _hasStoredFilter, whose declaration went with the
constants block.
Verified by the conformance harness (DuckDB vs SQLite, 20 servers x 28 filters)
and the golden snapshot, both unmoved — the constants are numerically identical,
so this is a sourcing change with no behaviour change. Full check/lint/test green
on both workspaces.