Skip to content

feat(app,web): name a relay on the map the way the app does - #733

Merged
efiten merged 2 commits into
efiten:masterfrom
khagele:feat/663-web-relay-names
Sep 30, 2026
Merged

efiten merged 2 commits into
efiten:masterfrom
khagele:feat/663-web-relay-names

Conversation

@khagele

@khagele khagele commented Sep 29, 2026

Copy link
Copy Markdown
Contributor

The map's ticker and point popup now name a relay the way the app does. A name on a 1 to 3-byte id wears the ~ guess mark (#452), and the one registry node within reach decides the name first (#661).

What you see

Row Before Now
Guest, name on a 2-byte relay id repeater-3 ~repeater-3
Member, one registry node in reach repeater-3 ~Mast Noord
Member, two or more in reach repeater-3 the id, no name
1-byte id placed on a node #a1 ~Mast Noord, with #a1 in the id column

Change

  • One file for how a name is printed. namerules.js holds isHashIdKind, GUESS_MARK, isGuessedName, nameParts and displayName, identical in app/src/ and web/ and pinned in web/parity.test.js. Both names.js files re-export it, so no import in the app changes. The app's behaviour is unchanged: the code moved.
  • The registry slice follows the rows. web/rowattribution.js asks /api/nodes/positions for the box around the rows it names, padded by the reach. The ticker shows the latest 200 receptions wherever they were heard, so a slice of the view would leave a line outside it without candidates.
  • Independent of the node-position layer. The names work with that layer off.
  • A guest asks nothing. The registry is refused below member, so every row reads by rule 2: the resolver's name with ~.
  • A slice is reused for 60 s while it covers the rows. The ticker polls every 5 s.
  • The ticker rewrites the sender cells of rows it already shows. It skipped a rebuild when the rows were the same, so a name that arrived later never reached a line. The cells are written in place and the scroll position stays.

Tests

  • web/rowattribution.test.js: the slice, the guest, the reuse, and an answer that is not a whole registry.
  • web/names.test.js and web/receptionticker.test.js: senderName and senderCell for every branch of the rule.
  • web/parity.test.js: namerules.js byte-identical, and senderCell equal on both surfaces for seven more senders, with and without an attribution.
  • web/e2e/relaynames.spec.js: five cases in the browser. The slice case fails when the ticker's attributor is given the view instead of the rows.
  • app vitest 1623 of 1623, web vitest 783 of 783, eslint clean in both, the app builds, e2e 302 of 302.

Limits

  • A truncated registry answer counts as no registry. A missing node is a missed candidate, and a collision would then read as a placement. Those rows read as a guest's. This can happen when the ticker's lines lie far apart.
  • The target picker keeps its own rule (AGENTS.md §7). This PR does not touch it.
  • The reach uses the raw RSSI, as the map's other surfaces do. The app uses the plotted one, so the two can differ by the hunter's offset.

Closes #663

🤖 Generated with Claude Code

@efiten

efiten commented Sep 30, 2026

Copy link
Copy Markdown
Owner

Review before merge: one point to change, one test to add, small ones. Found by reading the code at 101faaa, not run.

1. In "all" mode the ticker can fetch the registry twice per 5 s poll (change requested)
fetchAndRebuild attributes the filtered page and then the all page through the same tickerAttribution (web/rowattribution.js:653-656). Each fetch replaces slice with the latest rows' bounds. When a filter puts the filtered rows outside the all rows' box, neither slice covers the other, so each poll fetches twice and the 60 s reuse never applies. Each answer covers the whole padded box, and every ticker render waits on it.
Proposed: keep the slice as the union of the bounds it was fetched for (or one attributor per source), plus a unit test with two alternating boxes.

2. The in-place sender-cell rewrite has no deterministic test (change requested)
web/receptionticker.js:485-498 corrects lines drawn before the role or registry was known. The member e2e case only reaches it when the first ticker fetch lands before /api/auth/me, so removing those lines would likely not fail CI (§5.1). Proposed: a test that renders a row, refetches it with _attr changed, and asserts that .rx-id/.rx-sn changed while scrollTop stayed.

3. Small ones

  • A popup opened before the attribution lands shows the resolver's ~name for a prefix that is a collision, and is not refreshed when _attr arrives (web/map.js:385).
  • A failed registry answer is asked again on every 5 s poll; a short negative cache would bound it during an outage.
  • The popup's id line prints the bare sid (map.js:340), where the ticker shows #a1.

The map's ticker and point popup printed a name on a 1 to 3-byte relay
id as it came, without the guess mark, and never took a name from the
registry.

How a name is printed is now one file on both surfaces, namerules.js.
The map puts the attribution by reach on the rows it names, from a
registry slice around those rows. A guest has no registry and reads the
resolver's name with the guess mark.

Closes efiten#663

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@khagele
khagele force-pushed the feat/663-web-relay-names branch from 101faaa to 83a53d5 Compare September 30, 2026 18:01
@khagele

khagele commented Sep 30, 2026

Copy link
Copy Markdown
Contributor Author

Pushed 83a53d5 for the review, rebased on master. Per point:

  1. Two registry fetches per poll in "all". A fresh slice that does not cover the rows now grows to the box around both (rowattribution.js), so the filtered and the all page share one slice. Test: two alternating boxes over four polls ask twice, the second time for the box around both. Red with the union removed.
  2. The in-place rewrite. e2e/relaynames.spec.js draws 40 lines while the registry holds no node, scrolls the list, and lets the registry name the node a minute later (page.clock). The first and last line read "~Mast Noord" and scrollTop is unchanged. Red with the rewrite lines removed.
  3. Small ones.
    • A popup opened before the attribution lands is named again when it does: onPointClick waits on the draw's attribution and rewrites the popup while it is open. e2e with a held registry answer: "~repeater-3", then "~Mast Noord".
    • After an answer that is not a whole registry, the rows read without one for 15 s (RETRY_MS) instead of asking again every 5 s. Tested, red without the backoff.
    • The popup's id line reads #a1 for a hash, as the ticker does. Added to the 1-byte e2e case.

Web: 784 tests, eslint, Playwright 304 passed. App: 1628 tests, eslint, build.

@efiten
efiten merged commit a9aa01a into efiten:master Sep 30, 2026
6 checks passed
@github-actions github-actions Bot mentioned this pull request Sep 30, 2026
efiten pushed a commit that referenced this pull request Sep 30, 2026
🤖 I have created a release *beep* *boop*
---


<details><summary>app: 1.30.0</summary>

##
[1.30.0](app-v1.29.1...app-v1.30.0)
(2026-09-30)


### Features

* **app,web:** ease what the map shows by zoom, on one hex system
([#734](#734))
([4c2a57b](4c2a57b))
* **app,web:** name a relay on the map the way the app does
([#733](#733))
([a9aa01a](a9aa01a))
* **app:** send one broker to several collectors under one switch
([#722](#722))
([8c6da30](8c6da30))
* **web:** export the repeaters heard in view as a 1200×1200 PNG
([#725](#725))
([d6186bc](d6186bc))


### Bug Fixes

* **app,web:** hang a Discover-heard repeater's star from its own
position ([#724](#724))
([15a6ada](15a6ada))
* **app,web:** put the caret in the target picker's search when it opens
([#721](#721))
([6bd899c](6bd899c))
* **app:** drop the float window's previous and next buttons
([#717](#717))
([c957ff3](c957ff3))
* **app:** give the sound the playback buffer instead of the smallest
([#712](#712))
([dee5484](dee5484))
* **app:** hide Chrome's pause on the fullscreen readout
([#715](#715))
([836d91e](836d91e))
* **app:** keep a space under the HUD on Android
([#710](#710))
([2754716](2754716))
* **app:** let the fullscreen readout turn with the phone
([#718](#718))
([fae3749](fae3749))
* **app:** put the transmit pop under the receptions it sat over
([#738](#738))
([2bfc5fa](2bfc5fa))
* **server:** keep the node registry in memory on a loop of its own
([#736](#736))
([4f3e988](4f3e988))
* **web:** keep the map bar on one row between 641 and 767 px
([#732](#732))
([c44dc1d](c44dc1d))
</details>

<details><summary>server: 1.10.1</summary>

##
[1.10.1](server-v1.10.0...server-v1.10.1)
(2026-09-30)


### Bug Fixes

* **server:** keep the node registry in memory on a loop of its own
([#736](#736))
([4f3e988](4f3e988))
* **server:** read a refused frame again when the message-id decoder
changes ([#737](#737))
([8586c71](8586c71))
</details>

<details><summary>web: 1.26.0</summary>

##
[1.26.0](web-v1.25.1...web-v1.26.0)
(2026-09-30)


### Features

* **app,web:** ease what the map shows by zoom, on one hex system
([#734](#734))
([4c2a57b](4c2a57b))
* **app,web:** name a relay on the map the way the app does
([#733](#733))
([a9aa01a](a9aa01a))
* **app:** send one broker to several collectors under one switch
([#722](#722))
([8c6da30](8c6da30))
* **web:** export the repeaters heard in view as a 1200×1200 PNG
([#725](#725))
([d6186bc](d6186bc))


### Bug Fixes

* **app,web:** hang a Discover-heard repeater's star from its own
position ([#724](#724))
([15a6ada](15a6ada))
* **app,web:** put the caret in the target picker's search when it opens
([#721](#721))
([6bd899c](6bd899c))
* **app:** drop the float window's previous and next buttons
([#717](#717))
([c957ff3](c957ff3))
* **app:** give the sound the playback buffer instead of the smallest
([#712](#712))
([dee5484](dee5484))
* **app:** hide Chrome's pause on the fullscreen readout
([#715](#715))
([836d91e](836d91e))
* **app:** keep a space under the HUD on Android
([#710](#710))
([2754716](2754716))
* **app:** let the fullscreen readout turn with the phone
([#718](#718))
([fae3749](fae3749))
* **app:** put the transmit pop under the receptions it sat over
([#738](#738))
([2bfc5fa](2bfc5fa))
* **server:** keep the node registry in memory on a loop of its own
([#736](#736))
([4f3e988](4f3e988))
* **web:** keep the map bar on one row between 641 and 767 px
([#732](#732))
([c44dc1d](c44dc1d))
</details>

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
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.

web: the ticker and point popup name a relay without the ~ guess mark, and never from the registry

2 participants