Fix LiveView input guards, rover slider sync, and beacon pending list #23

Merged
graham merged 2 commits from fix/liveview-guards into main 2026-09-22 12:28:44 -05:00
Owner

Audit LiveView/TS group (part 2).

  • Unguarded client lat/lon crashed weather_map, weather_ca, rover, path LiveViews — is_number guards + fallthrough added.
  • Rover slider (max 18) vs handler clamp (48) disagreement fixed; set_slider push keeps them in sync.
  • /rover window keydown replaced with NavKeys hook (introduced in fix/liveview-hooks — merge that first or rebase).
  • Beacon pending-approval section tracks live pending_count; approved beacons stream_delete.
  • BeaconLive.Show approve no longer MatchErrors; rover popup Maps link gains longitude; LocationResolver range-checks coords; several swallowed failures now logged.

Verified: ~370 targeted tests + tsc green.

Audit LiveView/TS group (part 2). - Unguarded client lat/lon crashed weather_map, weather_ca, rover, path LiveViews — is_number guards + fallthrough added. - Rover slider (max 18) vs handler clamp (48) disagreement fixed; set_slider push keeps them in sync. - /rover window keydown replaced with NavKeys hook (introduced in fix/liveview-hooks — merge that first or rebase). - Beacon pending-approval section tracks live pending_count; approved beacons stream_delete. - BeaconLive.Show approve no longer MatchErrors; rover popup Maps link gains longitude; LocationResolver range-checks coords; several swallowed failures now logged. Verified: ~370 targeted tests + tsc green.
Fix LiveView input guards, rover slider sync, and beacon pending list
Some checks failed
skippy-bot/review Skippy review: 1 blocking finding open — see the PR thread
697fbc32f1
- Rover forecast handlers clamp to the slider's 18 h range and push a
  set_slider event so keyboard steps stay in sync with the slider
- phx-window-keydown on /rover replaced with the NavKeys hook (ignores
  arrows inside inputs/selects and the map container)
- point_detail/rover_cell_detail/gps_location handlers guard
  is_number(lat/lon) with a fallthrough — malformed payloads no longer
  crash the LiveView
- Beacon pending-approval section tracks a live pending_count and
  stream_deletes on approval instead of gating on the mount-time list
- BeaconLive.Show approve no longer crashes on {:error, _}
- Rover cell popup 'Open in Maps' link includes the longitude; the
  rescue fallback now logs
- LocationResolver rejects out-of-range lat/lon
- Logged previously-swallowed failures in seed_default_stations,
  live_table_resource export cleanup, and submit_live enqueue
skippy-bot left a comment

🤖 Skippy PR review

2 findings — 1 blocking before merge.

Severity Location Issue
🟠 High lib/microwaveprop_web/live/rover_live.ex:1037 NavKeys hook does not exist on main, so /rover loses its arrow keys
🔵 Suggestion lib/microwaveprop_web/live/rover_live.ex:40 Slider ceiling still duplicated instead of derived

Reviewed 697fbc32f180. Comment skippy review to re-run.

### 🤖 Skippy PR review **2 findings** — 1 blocking before merge. | Severity | Location | Issue | | --- | --- | --- | | 🟠 High | `lib/microwaveprop_web/live/rover_live.ex:1037` | NavKeys hook does not exist on main, so /rover loses its arrow keys | | 🔵 Suggestion | `lib/microwaveprop_web/live/rover_live.ex:40` | Slider ceiling still duplicated instead of derived | <sub>Reviewed `697fbc32f180`. Comment `skippy review` to re-run.</sub> <!-- skippy-pr-review -->
@ -37,1 +37,4 @@
@default_forecast_hour 0
# Must match `max` on the forecast-hour range input below — the slider
# ticks and the keyboard step clamp share this ceiling.
@max_forecast_hour 18
First-time contributor

🔵 Suggestion — Slider ceiling still duplicated instead of derived

The comment says this must match max on the forecast-hour range input, but the input hardcodes max="18" (~line 1506), so handler and slider can drift apart the same way they just did. Interpolate it in the template (max={@max_forecast_hour}) and the invariant is enforced instead of documented.

**🔵 Suggestion — Slider ceiling still duplicated instead of derived** The comment says this must match `max` on the forecast-hour range input, but the input hardcodes `max="18"` (~line 1506), so handler and slider can drift apart the same way they just did. Interpolate it in the template (`max={@max_forecast_hour}`) and the invariant is enforced instead of documented.
skippy-bot marked this conversation as resolved
@ -1012,2 +1035,3 @@
id="rover-page"
class="flex flex-col w-screen h-screen overflow-hidden bg-base-200 text-base-content"
phx-window-keydown="nav_key"
phx-hook="NavKeys"
First-time contributor

🟠 High — NavKeys hook does not exist on main, so /rover loses its arrow keys

NavKeys is not registered anywhere at this ref: assets/js/nav_keys_hook.ts is not in assets/js/ on main and app.ts has no NavKeys in its hooks map (both live only on the unmerged fix/liveview-hooks, PR #22). Merged standalone, LiveView has no hook to mount on #rover-page, and the phx-window-keydown="nav_key" binding this line replaces is gone, so left/right (forecast hour) and up/down (band) do nothing on /rover. The suite stays green because render_keydown/3 pushes nav_key straight into the LiveView and never touches the JS hook, and nothing else validates hook names (esbuild does not, mix xref does not). Land #22 first and rebase this onto it, or keep the window binding in place until NavKeys is on main.

**🟠 High — NavKeys hook does not exist on main, so /rover loses its arrow keys** `NavKeys` is not registered anywhere at this ref: `assets/js/nav_keys_hook.ts` is not in `assets/js/` on `main` and `app.ts` has no `NavKeys` in its `hooks` map (both live only on the unmerged `fix/liveview-hooks`, PR #22). Merged standalone, LiveView has no hook to mount on `#rover-page`, and the `phx-window-keydown="nav_key"` binding this line replaces is gone, so left/right (forecast hour) and up/down (band) do nothing on /rover. The suite stays green because `render_keydown/3` pushes `nav_key` straight into the LiveView and never touches the JS hook, and nothing else validates hook names (esbuild does not, `mix xref` does not). Land #22 first and rebase this onto it, or keep the window binding in place until NavKeys is on `main`.
skippy-bot marked this conversation as resolved
graham force-pushed fix/liveview-guards from 697fbc32f1
Some checks failed
skippy-bot/review Skippy review: 1 blocking finding open — see the PR thread
to 8025d33a49
Some checks failed
skippy-bot/review Skippy review: 2 blocking findings open — see the PR thread
2026-09-22 12:04:18 -05:00
Compare
skippy-bot left a comment

🤖 Skippy PR review

1 finding — 1 blocking before merge.

Severity Location Issue
🟡 Warning lib/microwaveprop_web/live/rover_live.ex:378 🟠 Warning: The rover cell popup this PR fixes cannot be reached from the browser

Follow-up: this push was a rebase/force-push (697fbc32 is no longer in the branch history), so the whole PR diff was re-read via FULL-DIFF-FALLBACK, not just the new commits. 2 earlier findings are still open at this head and were not reposted: the missing NavKeys hook (High, still absent from assets/js and app.ts, PR #22 still unmerged) and the slider ceiling still hardcoded as max="18" in the template (Suggestion, partially addressed: both clamps now use @max_forecast_hour).

Reviewed 8025d33a4996. Comment skippy review to re-run.

### 🤖 Skippy PR review **1 finding** — 1 blocking before merge. | Severity | Location | Issue | | --- | --- | --- | | 🟡 Warning | `lib/microwaveprop_web/live/rover_live.ex:378` | **🟠 Warning: The rover cell popup this PR fixes cannot be reached from the browser** | Follow-up: this push was a rebase/force-push (697fbc32 is no longer in the branch history), so the whole PR diff was re-read via FULL-DIFF-FALLBACK, not just the new commits. 2 earlier findings are still open at this head and were not reposted: the missing NavKeys hook (High, still absent from assets/js and app.ts, PR #22 still unmerged) and the slider ceiling still hardcoded as max="18" in the template (Suggestion, partially addressed: both clamps now use @max_forecast_hour). <sub>Reviewed `8025d33a4996`. Comment `skippy review` to re-run.</sub> <!-- skippy-pr-review -->
@ -360,3 +376,3 @@
end
def handle_event("rover_cell_detail", %{"lat" => lat, "lon" => lon}, socket) do
def handle_event("rover_cell_detail", %{"lat" => lat, "lon" => lon}, socket) when is_number(lat) and is_number(lon) do
First-time contributor

🟡 Warning — 🟠 Warning: The rover cell popup this PR fixes cannot be reached from the browser

Nothing in the tree pushes rover_cell_detail and nothing listens for show_cell_popup. A repo-wide grep for either name returns only this handler, push_cell_popup/3 (line 702, whose push_event on line 725 has no JS handleEvent for it) and the new test; assets/js/rover_map_hook.ts contains zero pushEvent calls, so the rover map never talks to the server the way propagation_map_hook/weather_map_hook do (point_detail). Consequence: the "Open in Maps" longitude fix on line 761 is unreachable in a real browser, the CHANGELOG bullet describing it tells users about a link they cannot click, and the new render_hook/3 test passes only because it injects the event directly, bypassing the missing client half. Either wire the cell click in rover_map_hook.ts (this.pushEvent("rover_cell_detail", {lat, lon}) plus a handleEvent("show_cell_popup") that opens the popup), or delete the dead handler/popup and drop the changelog line: a dead code path also means the is_number guard added here can never fire in production. Same root cause as the /rover arrow-key finding: this page's client half only exists on another branch.

**🟡 Warning — **🟠 Warning: The rover cell popup this PR fixes cannot be reached from the browser**** Nothing in the tree pushes `rover_cell_detail` and nothing listens for `show_cell_popup`. A repo-wide grep for either name returns only this handler, `push_cell_popup/3` (line 702, whose `push_event` on line 725 has no JS `handleEvent` for it) and the new test; `assets/js/rover_map_hook.ts` contains zero `pushEvent` calls, so the rover map never talks to the server the way `propagation_map_hook`/`weather_map_hook` do (`point_detail`). Consequence: the "Open in Maps" longitude fix on line 761 is unreachable in a real browser, the CHANGELOG bullet describing it tells users about a link they cannot click, and the new `render_hook/3` test passes only because it injects the event directly, bypassing the missing client half. Either wire the cell click in `rover_map_hook.ts` (`this.pushEvent("rover_cell_detail", {lat, lon})` plus a `handleEvent("show_cell_popup")` that opens the popup), or delete the dead handler/popup and drop the changelog line: a dead code path also means the `is_number` guard added here can never fire in production. Same root cause as the `/rover` arrow-key finding: this page's client half only exists on another branch.
graham force-pushed fix/liveview-guards from 8025d33a49
Some checks failed
skippy-bot/review Skippy review: 2 blocking findings open — see the PR thread
to ccd264e8ef
Some checks failed
skippy-bot/review Skippy review: 1 blocking finding open — see the PR thread
2026-09-22 12:28:13 -05:00
Compare
Author
Owner

Addressed in ccd264e8:

  1. NavKeys missing on main — resolved by merge order: #22 (which registers NavKeys in app.ts and ships nav_keys_hook.ts) merged first, and this branch is rebased on top of it. phx-hook="NavKeys" now resolves.
  2. Slider ceiling duplicated — the range input now reads max_forecast_hour() (a function returning @max_forecast_hour) instead of a hardcoded max="18", so template and handlers share one source.
Addressed in ccd264e8: 1. **NavKeys missing on main** — resolved by merge order: #22 (which registers `NavKeys` in `app.ts` and ships `nav_keys_hook.ts`) merged first, and this branch is rebased on top of it. `phx-hook="NavKeys"` now resolves. 2. **Slider ceiling duplicated** — the range input now reads `max_forecast_hour()` (a function returning `@max_forecast_hour`) instead of a hardcoded `max="18"`, so template and handlers share one source.
graham merged commit 221deb18ab into main 2026-09-22 12:28:44 -05:00
First-time contributor

Resolved 2 of 3 earlier findings:

  • NavKeys exists on main now (PR #22 landed) and /rover mounts it on #rover-page, so the arrow-key fix stands.
  • Slider ceiling is derived from @max_forecast_hour in both the input's max and every clamp.

Still open (1): the rover cell popup remains unreachable from the browser - rover_map_hook.ts never pushes rover_cell_detail and nothing handles show_cell_popup, so the Maps-link longitude fix and its CHANGELOG line are still user-invisible. This is now in main (merged as 221deb1).

Bookkeeping: the branch was force-pushed a second time (8025d33a is no longer in history), so the whole diff was re-read rather than just ccd264e8. No new defects in it; the is_number guards are safe (every JS caller pushes real numbers) and set_slider cannot loop (the hook's inputHandler only paints the label, it does not push back).

Resolved 2 of 3 earlier findings: - **NavKeys exists on main now** (PR #22 landed) and `/rover` mounts it on `#rover-page`, so the arrow-key fix stands. - **Slider ceiling is derived** from `@max_forecast_hour` in both the input's `max` and every clamp. Still open (1): the rover cell popup remains unreachable from the browser - `rover_map_hook.ts` never pushes `rover_cell_detail` and nothing handles `show_cell_popup`, so the Maps-link longitude fix and its CHANGELOG line are still user-invisible. This is now in `main` (merged as `221deb1`). Bookkeeping: the branch was force-pushed a second time (`8025d33a` is no longer in history), so the whole diff was re-read rather than just `ccd264e8`. No new defects in it; the `is_number` guards are safe (every JS caller pushes real numbers) and `set_slider` cannot loop (the hook's `inputHandler` only paints the label, it does not push back). <!-- skippy-pr-review -->
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
graham/prop!23
No description provided.