Skip to content

Release v26.3.3: Band Activity heatmap, mono-font picker, satellite resolver hardening#977

Merged
accius merged 38 commits into
mainfrom
Staging
May 12, 2026
Merged

Release v26.3.3: Band Activity heatmap, mono-font picker, satellite resolver hardening#977
accius merged 38 commits into
mainfrom
Staging

Conversation

@accius
Copy link
Copy Markdown
Owner

@accius accius commented May 12, 2026

Summary

Release v26.3.3. Cuts Staging into main — 37 commits, ~3.4k LOC.

New features

Bug fixes

  • fix(satellites) — two-commit fix for the TLE resolver:
    • 58f1cb6 hardens against CelesTrak rate-limit poisoning. Reported by Alan 2026-05-12: prod cache had stranded 9 sats (ISS, SO-50, AO-91, AO-123, SO-125, PO-101, EWS-G2, GK-2A, HIMAWARI-9) for ~7h after one bad refresh. CelesTrak rate-limits parallel CATNR lookups by returning HTTP 200 with empty bodies; the first batch of 5 hit that limit, SatNOGS doesn't carry the three weather geos, partial result poisoned the 12h cache. Drops per-NORAD parallelism 5→2, refuses to overwrite a materially worse cache.
    • d8948a3 follow-up: the previous commit's added retry expanded worst-case refresh latency past Cloudflare's 100s edge timeout, briefly stranding Staging users with 524s after redeploy. Drops the retry, shrinks per-request timeout 5s→4s, adds a single in-flight refresh Promise (concurrent /tle requests share one upstream pass), and adds stale-while-revalidate so any cached data is served immediately while refresh runs in the background. Only the very first cold-boot request blocks.
  • fix(display-schedule) ([BUG] TV screen power scheduler #901) — TVs latched into standby during scheduled sleep because OS released HDMI signal. New keepSignalActive flag (default on) holds wake-lock so HDMI stays alive and the TV stays awake on the black overlay. Opt-out checkbox in Settings → Display Schedule.
  • fix(dxcluster) ([BUG] N1MM UDP spot lines incorrect #961, reported by VK3GA) — N1MM UDP spot lines were drawing DX→user instead of DX→spotter. Restricted April's CONFIG.gridSquare fallback to self-spots only.
  • fix(whats-new) (fix(whats-new): OK button unreachable on mobile — use svh units #972) — OK button now reachable on mobile (svh units + iOS momentum scrolling).
  • fix(grid-display) — pass location object to latLonToMaidenhead, not bare lat/lon.

Refactor / tooling

Release notes

  • package.json / package-lock.json bumped 26.3.2 → 26.3.3 (commit ab61680).
  • WhatsNew modal gains a new 26.3.3 entry covering everything above; ANNOUNCEMENT banner updated to point at 26.3.3 content + Hamvention 2026 booth reminder (May 15–17, Booth #9518 in the Flea Market area).
  • Deployer note: vendor-download.sh must be rerun to pick up Fira Code / IBM Plex Mono assets, otherwise the new font picker falls back to system mono.

Test plan

  • npm run lang:check passes (CI gate)
  • Band Activity panel renders with live DX-cluster data
  • Mono-font picker switches actually take effect (after vendor-download.sh)
  • After a fresh Railway redeploy: hit /api/satellites/tle once, then /api/satellites/debug should show totalResolved ≥ 35/38 (the only legit holdouts EWS-G2/GK-2A/HIMAWARI-9 when CelesTrak throttles, since SatNOGS doesn't carry them)
  • No CF 524s during the cold-cache window after redeploy
  • Scheduled sleep window shows black overlay on TV without HDMI dropping
  • PSK Reporter trash-can clears TX+RX spots
  • N1MM UDP spot lines now run DX→spotter on non-self spots
  • WhatsNew modal: OK button tappable on iPhone/Android browsers; new 26.3.3 entry visible on first load after merge

🤖 Generated with Claude Code

MichaelWheeley and others added 30 commits May 10, 2026 10:27
append new keys to 'en' language
Adds npm run lang:check and npm run lang:sort.
The sort order in the js and mjs are the same order that VSCode natively uses with its built-in sorter.
85vh is measured against the full viewport including browser chrome
(address bar, nav buttons), so on mobile the modal could overflow the
visible area and push the footer button off-screen.

Switch to `100svh - 40px` (small viewport height minus the backdrop's
20px padding on each side), which always equals the guaranteed visible
area. 85vh is kept as a CSS fallback for older browsers that don't
support svh units.

Also adds -webkit-overflow-scrolling and overscroll-behavior: contain
to the feature list for smooth iOS momentum scrolling.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
fix(whats-new): OK button unreachable on mobile — use svh units
Workflow change to check src/lang/*.json key order, additional tools provided
Regression in 26.3.1 (commit c2bddcd): the UDP spot path unconditionally
filled `spotterGrid` with `CONFIG.gridSquare` when the payload lacked one.
For N1MM network broadcasts received from other contesters, this short-
circuits the downstream HamQTH/prefix lookups and pins every spotter at
the user's home, drawing lines from DX to the user instead of to the
real spotter (#961, reported by VK3GA).

Now only fall back to CONFIG.gridSquare when the spotter callsign matches
the user's own callsign (base, SSID-stripped). For all other spots, leave
spotterGrid null and let the existing HamQTH/prefix resolution locate
the real spotter.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds a trash-icon button in the PSKReporter panel header (between the
filter and refresh buttons) that wipes all currently displayed TX/RX
spots from local state. The map auto-clears on the next render since
its useEffect depends on the spots array.

Use case: contesters changing bands often have stale spots from the
prior band cluttering the map, especially when the new band is near
the old one and the band-color is similar. The existing band filter
takes multiple clicks; this is one click (#933, reported by tedszypulski-ui).

- Adds IconTrash to Icons.jsx
- Adds clear() to usePSKReporter hook (returns from hook surface)
- Button is disabled when there are no spots to clear
- New translation key pskReporterPanel.psk.clearTooltip across all 16 langs

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three small cleanups surfaced while diagnosing issue #875, where a user
set ROTATOR_HOST/ROTATOR_PORT/ROTATOR_PROVIDER=pstrotator_http and ended
up with UDP traffic going to our hardcoded 192.168.1.43 default — none
of their config was actually being read.

1. Remove ROTATOR_HOST / ROTATOR_PORT from server/config.js + server.js.
   They were exported into the context but never consumed by rotator.js,
   which reads PSTROTATOR_HOST / PSTROTATOR_UDP_PORT directly. Having
   both pairs in the codebase invited exactly the misconfig in #875.

2. Validate ROTATOR_PROVIDER against a known set ({none, pstrotator_udp}).
   Unknown values like 'pstrotator_http' previously triggered the UDP
   path silently; now we warn and disable the rotator instead.

3. Stop defaulting PSTROTATOR_HOST to '192.168.1.43'. When the provider
   is enabled but no host is set, log a warning and disable the rotator
   rather than leak a hardcoded LAN IP into other users' logs.

Also tightens the .env.example block to say the server only speaks UDP
and that 'pstrotator_udp' is currently the only non-'none' value.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
New BandActivityHeatmap panel mirrors DX-Heat's Band Activity view: a
continent×band matrix with gaussian-blurred SVG blobs colored by spot
count over the selected time window. Defaults match the DX-Heat layout
exactly — 6/10/12/15/17/20/30/40/80/160m rows, EU/NA/SA/AS/AF/OC columns,
60-minute window, blue→cyan→green→yellow→orange→red legend at the
0/9/11/13/19/20 stops.

Behavioral notes:
- The "Your continent" picker defaults to the continent derived from the
  user's callsign via cty.dat; user override is persisted to localStorage.
- A spot lands in column X on row B when the spot's band is B and at
  least one party (spotter or DX) is on the user's continent — the
  "other" party determines the column. Intra-continental spots count
  toward the user's own column.
- A 15/30/60 min window selector (DX-Heat only offers 60) is added, and
  per-cell numeric overlay shows the exact count for accessibility.

Wired into DockableApp as panel id 'band-activity'. Users add via the
panel picker; not in the DEFAULT_LAYOUT so existing layouts are unchanged.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The spot list shape produced by useDXClusterData stores the DX callsign
as `call`, not `dxCall` (see src/hooks/useDXClusterData.js:140). The new
BandActivityHeatmap panel was reading spot.dxCall — always undefined —
so the DX continent always resolved to null and every spot got filtered
out, leaving the matrix empty.

Now reads spot.call with a fallback to spot.dxCall in case the panel is
ever fed from a source that uses the path/proxy shape.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds a Settings → Display → Monospace font selector with three options:

- JetBrains Mono (default — dotted zero)
- Fira Code (slashed zero)
- IBM Plex Mono (slashed zero, slightly wider)

The reporter (N4HNH) couldn't reliably distinguish 0 from 8 in the
default font at small sizes. Fira Code and IBM Plex Mono both ship with
a slashed-zero variant that resolves the ambiguity.

Implementation:
- New --font-mono CSS variable in themes.css (theme-independent).
- ~210 inline JSX styles and CSS rules that hardcoded 'JetBrains Mono,
  monospace' swapped to var(--font-mono). Intentional override stacks
  (e.g. 'Orbitron, JetBrains Mono, ...') left alone, as are canvas
  ctx.font strings (CSS vars don't resolve in canvas).
- vendor-download.sh now pulls Fira Code and IBM Plex Mono alongside
  the existing fonts.
- useTheme reads openhamclock_monoFont from localStorage on init and
  applies the value to --font-mono; the dropdown writes the same key.
- New i18n key station.settings.monoFont(+.describe) across all 16 langs.

Heads-up for deployers: rerun bash scripts/vendor-download.sh to fetch
the new font files. Existing installs that don't run it will fall back
to system monospace when the user picks Fira Code or IBM Plex Mono.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
append new keys to 'en' language
…MichaelWheeley/openhamclock into language_translations_satellite_feature

# Conflicts:
#	src/lang/ca.json
#	src/lang/de.json
#	src/lang/en.json
#	src/lang/es.json
#	src/lang/fr.json
#	src/lang/it.json
#	src/lang/ja.json
#	src/lang/ka.json
#	src/lang/ko.json
#	src/lang/ms.json
#	src/lang/nl.json
#	src/lang/pt.json
#	src/lang/ru.json
#	src/lang/sl.json
#	src/lang/th.json
#	src/lang/zh.json
…ellite_feature

[Translation] [feature/satellite] Language translations for all satellite features
VK3GA reported that scheduled display sleep correctly blacks out the
screen but his TV then goes to standby because the host's DPMS kicks in
and kills the HDMI signal. TVs that latch into standby on signal loss
won't return when the signal comes back — only the remote will.

Root cause was intentional: useScreenWakeLock released the wake lock
when displaySleeping was true so the OS could power off the monitor
("so your TV or monitor can sleep" — the literal SettingsPanel text).
That's the right move for dedicated monitors but wrong for TVs.

Fix: add a displaySchedule.keepSignalActive flag, default true. When
held, the wake lock stays acquired through the sleep window — only the
black CSS overlay covers the screen, the HDMI signal continues, and the
TV stays "awake" (just showing black). At wake time the overlay clears
and the dashboard reappears with no remote intervention.

Users who want the monitor to actually power down can opt out via a new
checkbox under the time pickers. The explainer text now describes both
modes and points TV users at the setting.

- Config default flips to keepSignalActive: true (fix-by-default)
- Existing saved configs without the flag inherit the new default via
  `keepSignalActive !== false` reads in both the hook and the panel
- No change for users who untoggle the new checkbox

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Self-contained Reveal.js presentation about OpenHamClock, originally
written for the DVRA (W2ZQ) club night on 2026-05-13. Designed to also
serve as a template that other hams can fork for their own club talks.

- `presentations/README.md` indexes available decks
- `dvra-w2zq-2026-05-13/index.html` is the deck (16 slides, 16:9, CDN-loaded reveal.js, no build step)
- Per-deck README documents how to present, customize, and trim
- `speaker-notes.md` has script-grade talking points, transitions, and a Q&A bank

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
accius and others added 6 commits May 11, 2026 23:18
- Title and closer slides get the OHC logo (explicit centering that
  beats reveal theme's `.reveal section img` rule)
- Hero screenshots on the big-picture and map slides (1.png, 2.png)
- Side-panel screenshots on DX cluster, activations, satellites
- 3-up screenshot grid on the propagation slide
- Live-demo slide gets a ghosted full-slide background screenshot
- Install slide reflowed to a 2x2 card grid with proper CLI wrapping
  (min-width:0 + overflow-wrap:anywhere fixes grid overflow)
- Closer slide tightened so "Questions?" fits without cutoff
- WB0OEW SK date corrected to early 2026; quote line dropped on slide 3
- Final slide no longer leads with Elwood; switched to contributors-only
- New DVRA-member callout thanking N2EHL (Rich Freedman) on the DX
  cluster and closer slides for his cluster-filtering contributions

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
[Fix] use language coded version of 'VISIBLE' inside prediction table
…re lat/lon

d1ab3d5 replaced calculateGridSquare(lat, lon) with latLonToMaidenhead but kept
the two-positional-arg call style. latLonToMaidenhead expects ({lat, lon}) as its
first argument; passing a bare number caused lat/lon to destructure as undefined,
producing NaN and invisible characters in both DE and DX grid squares.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
fix(grid-display): restore Maidenhead grid square in DE/DX panels
…soning

Production cache was holding 9 unresolved sats (ISS, SO-50, AO-91, AO-123,
SO-125, PO-101, EWS-G2, GK-2A, HIMAWARI-9) for ~7h after a single bad
refresh. Root cause: CelesTrak rate-limits parallel CATNR lookups by
returning HTTP 200 with an empty body; the first batch of 5 hit that
limit, fell through to SatNOGS which doesn't carry the weather geos, and
the partial result poisoned the 12h cache.

- Drop per-NORAD parallelism from 5 → 2 and bump inter-batch delay to 500ms
- Retry CelesTrak CATNR once after 1s on empty/short responses before
  giving up to SatNOGS
- If a refresh ends up materially worse than the previous cache (5+ sats
  unresolved, prior cache still within stale-serve window), keep the old
  cache and mark X-TLE-Stale instead of overwriting

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The previous resolver hardening (58f1cb6) made the cold-start refresh path
worse, not better: per-NORAD retries + smaller batches pushed worst-case
refresh latency past Cloudflare's 100s edge timeout. Users hitting /tle
during a cold-cache window got 524s; the CDN then cached the error HTML
for an hour, breaking the satellite layer for everyone in that region.

- Drop the 1s CelesTrak CATNR retry — kept smaller batches (2-parallel)
  and 400ms inter-batch delay, which already addresses the original
  CelesTrak throttling without doubling worst-case latency
- Per-request timeout 5s → 4s to keep total worst case under CF window
- Add a single in-flight refresh Promise (refreshInFlight) so concurrent
  /tle requests share one upstream refresh instead of fanning out N
  parallel CelesTrak hammering attempts
- Add stale-while-revalidate: if any cached data exists, return it
  immediately and kick the refresh off in the background. The only
  request that blocks on a slow refresh is the very first one after a
  cold boot — no chance of CF 524 once the cache has been seeded
- Keep the "refuse to overwrite a materially worse cache" safeguard

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@railway-app railway-app Bot temporarily deployed to openhamclock / Staging May 12, 2026 12:11 Inactive
Post-26.3.2 accumulated work:
- NEW: Band Activity heatmap dockable panel (#913)
- NEW: User-selectable monospace font in Settings (#923)
- NEW: PSK Reporter clear-all-spots button (#933)
- fix(display-schedule) #901: TV-standby HDMI keep-alive
- fix(satellites) 58f1cb6/d8948a3: CelesTrak rate-limit resilience +
  stale-while-revalidate to avoid CF 524s during cold-cache windows
- fix(dxcluster) #961: N1MM UDP spot lines run DX→spotter again (VK3GA)
- fix(satellites) #970/#973: localized "VISIBLE" badge (Michael Wheeley)
- fix(whats-new) #972: OK button reachable on mobile (DO1HOZ)
- fix(grid-display): pass location object to latLonToMaidenhead (DO1HOZ)
- refactor(rotator): drop dead config knobs, validate ROTATOR_PROVIDER
- CI: lang:sort/lang:check gate for src/lang/*.json (#971, Michael Wheeley)

Updates ANNOUNCEMENT banner to point at 26.3.3 content and the
Hamvention 2026 booth (May 15-17, Booth #9518).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@railway-app railway-app Bot temporarily deployed to openhamclock / Staging May 12, 2026 12:18 Inactive
@accius accius changed the title Release: Band Activity heatmap, mono-font picker, satellite resolver hardening + bugfixes Release v26.3.3: Band Activity heatmap, mono-font picker, satellite resolver hardening May 12, 2026
…dex.html

Unblock the format CI check for PR #977 — file was added in b3492a8 without
having prettier run over it.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@railway-app railway-app Bot temporarily deployed to openhamclock / Staging May 12, 2026 12:31 Inactive
@accius accius merged commit 2bbaa2a into main May 12, 2026
11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants