fix(wireless): keep the 2.4 GHz channel map inside its card (v0.23.13-beta)

The 2.4 GHz row of the Channel Usage panel painted over the edge of the card.

The cause is the band table, not the layout. A 40 MHz channel 1 occupies
2392-2432 MHz, but the drawn 2.4 GHz band started at 2400, so that carrier
resolved to `left: -8%` and an absolutely positioned bar at a negative offset
renders outside its container. 2.4 GHz is the only band where this can happen:
channel 1 is the lowest channel in the plan and the only one whose 40 MHz form
reaches below the round number the axis started on.

The band now starts at 2390, which is also more honest — the radio really does
reach below 2400, and clamping alone would have drawn it as though it did not.

Geometry moves to utils/spectrumGeometry.ts with tests, and keeps a clamp even
though the range now covers channel 1: band ranges are static assumptions, and
region-specific channels and widths they do not anticipate will keep appearing.
No bar can leave the card again regardless of what the backend reports.

An earlier attempt at this fixed a real but different bug in the same row: ticks
at 2400 and 2480 fall outside the channel plan, so an axis labelled by channel
fell back to printing raw frequencies, and the 2400 label sat at exactly 0% with
a centring transform and hung half its width outside the card. That fix stands;
it simply was not the whole of what the user was seeing. The verification script
used at the time filtered radios to those starting inside their band, which
silently discarded the very row that was overflowing.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2GT-Rich
2026-08-23 13:35:22 -07:00
co-authored by Claude Opus 5
parent 2625fea8de
commit ec873ab8bd
9 changed files with 127 additions and 11 deletions
+10 -4
View File
@@ -12,6 +12,7 @@ import {
BAND_LABEL, RfBand,
RSSI_ZONES, rssiColor, RETRY_BUCKETS, retryBucketIndex, retryColor,
} from '../../utils/wifiChannels';
import { barGeometry } from '../../utils/spectrumGeometry';
const ALL_BANDS: RfBand[] = ['2.4', '5', '6'];
@@ -60,7 +61,11 @@ function SpectrumBandRow({ band, radios }: { band: RfBandRange; radios: RfChanne
</div>
<div className="relative rounded" style={{ background: 'var(--surface-3, #e5e7eb)', minHeight: lanes.length ? lanes.length * 16 + 4 : 12 }}>
{lanes.map((lane, li) => lane.map((r) => (
{lanes.map((lane, li) => lane.map((r) => {
const { leftPct, widthPct } = barGeometry(
r.low_mhz, r.high_mhz, band.startMhz, band.endMhz,
);
return (
<div
key={`${r.device_id}:${r.name}`}
title={[
@@ -72,15 +77,16 @@ function SpectrumBandRow({ band, radios }: { band: RfBandRange; radios: RfChanne
].join('\n')}
className="absolute rounded-sm border border-black/10 dark:border-white/10"
style={{
left: `${pct(r.low_mhz)}%`,
width: `${Math.max(0.6, ((r.high_mhz - r.low_mhz) / span) * 100)}%`,
left: `${leftPct}%`,
width: `${widthPct}%`,
top: li * 16 + 2,
height: 12,
background: OVERLAP_STYLE[r.overlap].fill,
opacity: 0.85,
}}
/>
)))}
);
}))}
</div>
<div className="relative h-3 mt-0.5">