Skip to content

fix(app,web): hang a Discover-heard repeater's star from its own position - #724

Merged
efiten merged 2 commits into
efiten:masterfrom
khagele:fix/723-discover-star-on-node
Sep 30, 2026
Merged

efiten merged 2 commits into
efiten:masterfrom
khagele:fix/723-discover-star-on-node

Conversation

@khagele

@khagele khagele commented Sep 27, 2026

Copy link
Copy Markdown
Contributor

Closes #723

What

A Discover reply carries a prefix of the responder's key. The node layer paired it with the registry node whose key it alone starts (#661). The reach stars did not: starKey gave the prefix a star of its own, and positionOf looked it up as a whole key, which a prefix never is. So a repeater heard by Discover drew its ▲ and, beside it, a star hung from its RSSI estimate with the prefix as its name.

The pairing is now one function, registryMatcher(nodes) in nodelayer.js:

  • groupSenderPointsForNodes uses it where it had the same loop inline;
  • coverageStars takes it as registryNodeOf: a hearing whose own id names one registry node joins that node's star, keyed by its pubkey, under its ▲;
  • a prefix that starts two keys or none keeps a star of its own from its estimate, as before;
  • a relay, path or direct hash still goes through the attribution by reach first.

Both copies of nodelayer.js change the same way; coverage.js is copied whole. web/parity.test.js adds registryMatcher to the shared core and checks both surfaces name the same node. Amended in docs/2026-09-08-coverage-overview.md (point 8).

Effect on real data

On the /api/points export of 8 Aug to 7 Sep around Nijmegen: before, 5 of 66 stars hung from their ▲ and 61 from an estimate; after, 63 of 71 hang from their ▲ and 8 from an estimate. The total grows because a prefix with too few hearings for an estimate had no origin at all, and now hangs from its node.

Found while building the #666 export, which draws the stars as the map has them.

Verification

  • coverage.test.js: a prefix's hearings join the node's star with its advert; a prefix that starts two keys keeps its own; a relay hash is left to the attribution. The first is red with the pairing removed.
  • nodelayer.test.js: registryMatcher names the node for a discover prefix and an advert key, none for an ambiguous or unknown prefix, none for a relay hash.
  • app: 1630 tests green, eslint clean, npm run build clean
  • web: 763 tests green, eslint clean, full e2e 297 passed

🤖 Generated with Claude Code

@efiten

efiten commented Sep 30, 2026

Copy link
Copy Markdown
Owner

Review before merge: one point to change, one question. #725, #726 and #729 sit on top of this one. Point 1's call sites checked at af00c86; the effect is read from the code, not run.

1. Dot hue and selection dimming still key a Discover hearing by its prefix (change requested)
coverageStars now keys a registry-matched Discover hearing by the node's full pubkey (coverage.js:202-204), but four callers still compute the key without registryNodeOf:

  • app/src/huntmap.js:196 (pointHue) and :204 (ownerOf)
  • web/map.js pointHue and owner in selectionDimmer (around lines 1642-1648)

For a discover_pubkey hearing they get the prefix, which no star has any more. In reach mode:

Proposed: one starKeyOf(pt) in coverage.js that coverageStars, pointHue and ownerOf/owner all use, plus a test that a Discover hearing's key equals its star's id.

2. Question: the app pairs over two different node sets
huntmap.js:221 builds the matcher from all nodePositions; the node layer pairs over inView (:1058). A prefix unique in view but ambiguous across the registry is paired on the node layer and not in the stars. It needs two keys sharing an 8-byte prefix. Pair both over the same set, or note the difference in the doc?

…tion

A Discover reply carries a prefix of the responder's key. The node layer
paired it with the registry node whose key it alone starts (efiten#661); the reach
stars did not, and looked the prefix up as a whole key, which it never is.
So the repeater drew its ▲ and, beside it, a star hung from its RSSI estimate
with the prefix as its name.

The pairing is now one function, registryMatcher in nodelayer.js, which
groupSenderPointsForNodes and coverageStars both use; AGENTS.md §7 names
both. On the data of 8 Aug to 7 Sep around Nijmegen, 63 of 71 stars now hang
from their ▲, where 5 of 66 did.

Closes efiten#723

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@khagele
khagele force-pushed the fix/723-discover-star-on-node branch from af00c86 to 82588a0 Compare September 30, 2026 17:27
@khagele

khagele commented Sep 30, 2026

Copy link
Copy Markdown
Contributor Author

Pushed 82588a0 for the review, rebased on master. Per point:

  1. Dot hue and dimming. starKeyOf(pt, { attributionOf, registryNodeOf }) in coverage.js holds the rule; coverageStars builds its keys with it, and pointHue/ownerOf in huntmap.js and pointHue/owner in web/map.js ask it with the two functions the last draw's stars were built with. A test keys each hearing of the app,web: a repeater heard by Discover hangs its reach star from an estimate, beside its own ▲ #723 fixture (two Discover, one advert) and compares it with its star's id; red with starKeyOf reading only the reach. The amendment in docs/2026-09-08-coverage-overview.md names it.
  2. Two node sets in the app. Should both pair over the same set? The stars pair over all node positions, the node layer over the nodes in view. Over all nodes the pairing does not change with the view; over the nodes in view it matches what the node layer shows. Or leave the code and note the difference in the doc?

App: 1637 tests, eslint, build. Web: 763 tests, eslint, Playwright 297 passed.

@efiten
efiten merged commit 15a6ada into efiten:master Sep 30, 2026
6 checks passed
@github-actions github-actions Bot mentioned this pull request Sep 30, 2026
efiten added a commit that referenced this pull request Sep 30, 2026
Closes #666

**Builds on #724:** the first commit is that PR (a Discover-heard
repeater's star under its own ▲). Review the second; it rebases to one
commit once #724 is in.

## What

An **Export** button in #bar, next to Start mapping, opens a sheet with
the first export of #665: **Repeaters heard**, the map as it stands
(filters, window, hunters) as a 1200×1200 PNG, saved as
`mesh-hunter-repeaters-heard-YYYY-MM-DD.png`. Below 640px the button
moves into the menu with Start mapping and Log in (`barnarrow.js`,
#561).

The picture shows the stars the reach layer would draw for the view,
from the same registry slice, window receptions and attribution, whether
the layer is on or not:

- every repeater in its own hue (`assignHues`), each ray's strength as a
gradient within it: strong full and thick, weak lighter and thinner,
with the map's width and opacity (`rayStyle`);
- a dot per hearing, the ▲ or ● the star hangs from, and the names that
fit (most-heard first, dropped on overlap, never the ▲);
- the mapped cells (the resolution closest to 360 Mercator units, res 8
today) and the route in grey; a star whose nearest hearing is more than
30 km off is left out and named;
- a band with the mark, the title, the window as dates, up to three
hunters, three numbers (repeaters, receptions, farthest) and a legend;
- always the light theme.

Decided with Kasper on mockups rendered from a real `/api/points`
export; what was tried and not chosen (signal-tier colours, #720's hex
fill over every repeater) is in
`docs/2026-09-27-export-repeaters-heard.md`.

## How

- `exportheard.js`: the rules (which stars, the numbers, the route, the
cells, label placement, the texts), unit-tested.
- `exportrender.js`: a second MapLibre instance offscreen at 1200×1000
with `preserveDrawingBuffer` (the live map is untouched), then a canvas
for the glyphs, names, scale bar and band. The bare background stands in
when the hosted style does not load.
- `exportsheet.js`: the sheet, on the `.lc-modal` shell with the
settings sheet's focus rules.
- Members only, like the node positions it is made of; anyone else sees
the item disabled with the reason.
- `hexgrid.js` is copied whole from the app and added to the parity
list, which #720 needs too.

## Verification

- `exportheard.test.js` (18): what is drawn, the 30 km rule, the
numbers, the route breaks, the cells, label placement, tint, window
text, hunters, file name, scale bar. Five rules mutated, each caught.
- `e2e/export.spec.js`: a member downloads a PNG of 1200×1200 with the
right name; a guest sees the reason and a disabled button; Escape closes
and hands focus back. Red with the sheet unwired.
- Rendered in Chromium with the real export of 8 Aug to 7 Sep around
Nijmegen: 87 repeaters, 4501 receptions.
- web: 789 tests green, eslint clean, full e2e 301 passed

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: efiten <erwin.fiten@gmail.com>
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.

app,web: a repeater heard by Discover hangs its reach star from an estimate, beside its own ▲

2 participants