feat(app): let the HUD follow the ticker's stand, act on the shown sender, and float it over other apps - #575
Open
khagele wants to merge 2 commits into
Open
feat(app): let the HUD follow the ticker's stand, act on the shown sender, and float it over other apps#575khagele wants to merge 2 commits into
khagele wants to merge 2 commits into
Conversation
…t on the shown sender The HUD was written from every capture, before the filter ran: narrow the map to one target and the readout still jumped to every passer-by (efiten#555). The receptions ticker already had a filtered/all toggle, and Kasper's steer was that with a filter set you look at the filtered set on both, so this is one stand for both surfaces, flipped from either. state.rxMode holds the stand; createReceptionLog reports its header toggle through onModeChange and takes setMode back. In filtered mode a reception the filter keeps off the map stays off the HUD, and the HUD keeps the last one that passed with that reception's own RSSI, SNR, sender and age. A closed eye on the pill says the filter kept something out since then; the count is in the aria-label and tooltip. Flipping to all shows the last capture at once. Three quick actions on the shown sender, Target, + Target and Ignore, dispatch the events the map popup and the target sheet already use. Target and + Target follow the list's own isTargetKind rule; Ignore needs only an id. The pure rules live in hudmode.js; app.js keeps the glue. The FAB stack moves up 34 px, the height of the new row, so the clearance above the HUD stays what efiten#264 measured (60 px, measured on this branch at 360 and 412). Left out on purpose: picture-in-picture, which gets its own design round; efiten#453 (re-render once a name resolves) stays open. Verified: app 912 unit tests, build and eslint clean; web 509 unit tests and the whatsnew e2e (changelog copies). Seven mutation checks, each red on one test. Browser at 360 and 412: two hidden receptions show the eye, All shows the last capture, the ticker toggle flips the HUD, Target and Ignore act and read active. Row 308 of 328 px at 360. Part of efiten#555 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… the window's buttons Leave the app and the hunt had nothing to look at (efiten#555). Float draws the HUD's reading onto a canvas, streams it into a <video> and takes that fullscreen. Chrome for Android moves a fullscreen video into its picture-in-picture window by itself when you press Home or switch apps; Chrome's Media Session auto-PiP is desktop-only, so fullscreen is the path. captureStream(0) with requestFrame per draw, so nothing depends on requestAnimationFrame, which a hidden page never runs. The window is the HUD: tier colour as tint and left bar, RSSI in white, SNR and age, the sender through senderReadout, the stand with the eye, the BLE and MQTT dots, and Disconnected in amber. Android's previous/next buttons on the window scrub the ticker's playhead (rxStepIndex, step, active, following on the ticker), so the window steps through the list the ticker shows, each reception with its own age; the newest row makes it follow again. Where the browser lacks canvas capture or a way to take a video out of the page, the button is not shown. The row reorders per the design round: Target, Add, Ignore, Float, then the stand pill at the right edge. Add and Ignore keep one word in every state so the row holds five controls at 360 px (302 of 328 px, 320 with the eye). Verified: app 926 unit tests, build and eslint clean; web 509 and the whatsnew e2e. Browser at 360: the canvas draws as designed, stepping back shows the previous reception with its own age, stepping forward to the newest follows again, closing pauses the stream and resets the button. Not verified here: Android floating a muted canvas video the way it floats a clip, and the page keeping its timers while it does. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #555
The problem
The HUD was written from every capture, before the filter ran. Narrow the map to one target and the readout still jumped to every passer-by on the public channel. It was read-only: selecting or ignoring that sender meant opening the target sheet. And the moment another app came in front, there was nothing left to look at.
My changes in this PR
Two commits, one per half of the issue.
One filtered/all stand for the ticker and the HUD (
state.rxMode). The ticker's header toggle reports throughonModeChangeand takessetModeback; the HUD'sFiltered/Allpill flips the same stand. With a filter set you look at the filtered set on both. In filtered mode the HUD keeps the last reception the filter let through, with that reception's own RSSI, SNR, sender and age. A closed eye on the pill says the filter kept something out since then; the count is in thearia-labeland tooltip. Flipping to All puts the last capture on the HUD at once.Quick actions on the shown sender:
Target(only this one, the popup's Isolate),Add(to the selection) andIgnore, through the events the map popup and the target sheet already use. Target and Add follow the list's own rule, exported asisTargetKind. The pure rules are inapp/src/hudmode.js.The float readout (
app/src/floatreadout.js).Floatdraws the HUD's reading onto a canvas, streams it into a<video>and takes that fullscreen. Chrome for Android moves a fullscreen video into its picture-in-picture window by itself when you press Home or switch apps; Chrome's Media Session auto-PiP is desktop-only, so fullscreen is the path. The window shows the tier colour as tint and bar, the RSSI, SNR and age, the sender, the stand with the eye, the BLE and MQTT dots, andDisconnectedin amber. Android's previous/next buttons on the window (Media Sessionprevioustrack/nexttrack) scrub the ticker's playhead, so the window steps through the same list the ticker shows. Where the browser lacks canvas capture or a way to take a video out of the page, the button is not shown.FAB stack +34 px, the height of the new row, so the clearance above the HUD is unchanged. Decision log:
docs/2026-09-04-hud-follows-the-filter.md. Changelog entries in both copies.Verification
whatsnew.spec.js,--fail-on-flaky-testsSeven mutation checks on the HUD tests, each red on one test. Browser at 360x780 and 412x915, feeding a signed advert through the capture path from the console: two receptions kept out by a target filter show the eye; All shows the last capture; the ticker's toggle flips the HUD's pill; Target sets the chip and reads active. The row uses 302 of 328 px at 360 with the float button, 320 with the eye. FAB clearance above the HUD is 60 px on both widths, as before. The float canvas draws as designed, stepping back moves the playhead to the previous reception with its own age, stepping forward to the newest makes it follow again, and closing pauses the stream and resets the button.
Not verified here, and worth a field check: whether Android Chrome floats a muted canvas-stream video the way it floats a playing clip, and whether the page keeps its timers while it does. The browser pane cannot go fullscreen without a real tap. The capture path is unchanged either way.
Decisions worth a look
updateHudstill renders from the capture-time record; app: the HUD sender freezes at capture time, so a name that resolves later never reaches it #453 stays open.fullscreenchangehandling waits 600 ms before treating a fullscreen exit as a close, because Android hands a fullscreen video to its PiP window in two steps.🤖 Generated with Claude Code