feat(app): make the HUD the ticker's playhead, so a name that resolves later reaches it - #583
feat(app): make the HUD the ticker's playhead, so a name that resolves later reaches it#583khagele wants to merge 3 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>
…s later reaches it The HUD's sender line was written once, at capture, before the resolver had answered; the ticker rebuilds from enriched rows every tick, so one packet read "repeater_3_" there and "via 2beb" on the HUD. Two readouts that agree while following and drift as soon as you scrub: since efiten#555 the float window moved with the playhead and the HUD under it did not. One readout now (Kasper, 2026-09-05). The HUD shows the reception on the ticker's playhead: a tap or a step moves it, a reception that passes the filter puts the ticker back on the newest row and lands on the HUD at capture, and every tick the enriched playhead row redraws it when it reads differently, which is how the name lands, through senderReadout, with the # on a hash id kept. Closes efiten#453 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
85925e8 to
708a87a
Compare
|
Master moved: #569, #574, #580, #581 and #589 are merged, master is at Notes below are from reading this PR's diff against master before those merges. Not a full audit, and the green checks are not part of it. Nothing found to change in the diff itself. Two ordering notes. #575's two commits are in this branch, so #575 is superseded by it rather than a separate merge. And this touches the same FAB stack offsets in CI can fail this on |
Closes #453
Stacked on #575 (
feat/555-hud-pip): that PR owns the HUD'sshowOnHudpath and the float readout this change hangs off. Merge #575 first; this one then rebases to its own diff.The problem
The HUD's sender line is written once, at capture, before the resolver has answered. The ticker rebuilds from enriched rows every tick, so the same packet read
repeater_3_there andvia 2bebon the HUD for as long as it stayed on screen (field screenshot in the issue). AGENTS.md §5.4 rule 3: rendering from data that arrives later needs a re-render when it does.The issue asked to keep the HUD on the last reception, separate from the ticker's playhead (#309). That model had grown a third readout since: the float window (#555) follows the playhead when scrubbed, while the HUD under the same thumb does not. Kasper's call on refinement: one readout.
My changes in this PR
onActiveChangeputs the playhead row on the HUD, so a tap, a step from the float window and a marker tap move it (app/src/app.js). A reception that passes the filter always reaches the HUD: it puts the ticker back on the newest row if a scrub had moved it (follow()on the ticker) and goes on the readout at capture, before the tick. Flipping the stand rebuilds the ticker and follows again, so All still shows the last capture at once.syncHudToPlayheadcompares the playhead row with what is shown throughsameReadout(app/src/hudmode.js: same reception, same sender line) and redraws on a difference. That is how the name lands: throughsenderReadout, so the#on adirect_hashand apath_hashstays.docs/2026-09-05-hud-is-the-playhead.md; the app: leaving the app throttles the hunt, and the HUD offers nothing to act on #555 log points at it. Changelog entry in both copies.Verification
whatsnew.spec.js,--fail-on-flaky-testssameReadouthas three tests, mutation-checked: the same row again is the same readout, a landed name is not (red with the label comparison removed), another reception is not.Browser, resolver stubbed, two receptions in the store: following shows the newest (
alpha, 30s); one step back shows the older one (via abcd12, 1m 0s); two ticks later, still scrubbed, it readsvia repeater_3_with the same reception and age. Then, scrubbed again, a frame throughprocessFrame(undecodable, -77 dBm, inside the filter): the HUD shows it at once, the ticker follows again, and the next tick keeps it there.Not in this PR
onActiveChange.🤖 Generated with Claude Code