Repository navigation
feat(web,landing): lead with mapping, and say where accounts come from - #491
Conversation
b5a9b00 to
783d58c
Compare
|
Rebased onto master for #497. The only conflict was One thing that changed with the rebase and is worth stating rather than leaving to be noticed: #497 filled the toolbar up, so
Short label and icon-only were both measured too: at 1440 the bar wraps either way, so the row is the price of having the control at all, not of its wording. Kept, deliberately. The map is downstream of the RX webapp, and the one visitor this whole PR exists for is the one who has never mapped — 36 px of map height is what that costs on desktop. The mobile breakpoint still shortens the label to "Map it", where the bar has the room. web 423, landing 35, playwright 166, eslint clean. |
The login card offered logging in and nothing else, so a visitor who had never made an account had nowhere to go. The map cannot register anyone either: /api/auth/register rejects a body without a companion_pubkey, so only the RX webapp can. Reported in Discord by someone who went looking for a sign-up page and found none. Three places now say it. The login card carries a footer line linking rx.mesh-hunter.eu; the guest notice names it instead of ending at "Log in to see more"; and the landing gets a "Get an account" section with the four steps through to admin member verification, moved out of the small print at the foot of the page. The notice wording is length-bound: #bar is flex-wrap and the notice shares its row with the SF counts, so a longer line costs the map a bar row. Measured at 375/1024/1280/1440: bar height unchanged. Closes efiten#490
The RX webapp is the product: you pair a companion to it and map the mesh, and this map is where every mapper's receptions meet. Both sites led with the map instead, which sends a first-time visitor to a login screen for data they have no part in yet. The landing now opens on "Start mapping", puts the RX webapp section before the map, and says the pairing in the hero; the nav on all four pages lists the RX webapp first. The map gains #rx-cta, a Start mapping link in the bar next to Log in, and the walkthrough explains it and says where mapping happens. Mapper and hunter stay both words, as the landing now states: mapping is what you do with the radio, hunting is going after one node, and it is one account either way. The role names are untouched. Two measurements. #rx-cta costs no bar row at 1024/1280/1440, and at 375px only with its long label, so it shortens to "Map it" below the 640px breakpoint (measured: the row holds ~80px, "Start mapping" is 109px). The guest notice goes back to "Log in to see more" for the same reason -- naming the RX webapp there cost a row, and the button beside it now carries that message. Closes efiten#490
That line was our own vocabulary note, written into the page by mistake. The reader does not need the distinction spelled out; the steps above it already say what happens.
CI caught the account callout 900px from the button it describes. #bar is flex-wrap, so Start mapping and Log in share a row at some widths and sit on separate rows at others, and a callout anchored to both is placed against the union of the two: with the buttons split across rows, the union spans the whole bar and the box lands in the empty middle. The widths that reproduce it depend on font metrics, so a Mac never showed it. Each button now has its own box. The sweep that would have caught this goes with it, and it forces the row break with a margin instead of hoping a viewport width produces one -- mutation-checked by restoring the shared anchor, which fails only the forced-split cases.
The sweep added with the callout fix tried to force Start mapping and Log in onto separate rows with a 9999px margin, so the union placement could be proved wrong on any machine. CI failed on that case: at 1440px the forced layout puts the account box somewhere the assertion does not allow, and neither placement is the one a reader would ever see. A narrower bar does not work either -- the tour drops to its inline panel before the split happens, so there is no box left to measure. The width sweep stays and the assertion is unchanged. What actually rules the shape out is the one-target-per-button test in onboarding.test.js, and both comments now say so instead of claiming the sweep would have caught it.
parity.test.js asserts that the dates in changelog.json do not increase down the file. Master gained several notes dated 2026-08-25 while this PR was open, and the rebase left this branch's own 08-25 entry sitting below an 08-24 one. Order only, in both copies. Same entries, same ids, same text, so nobody's seen-state moves. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
783d58c to
26547cb
Compare
efiten
left a comment
There was a problem hiding this comment.
Reviewed the code; the product call is the maintainer's. Everything I checked holds up, and the one thing I would ask for is a re-run rather than a change.
The green here is stale, and it matters for this PR in particular
CI last ran at 04:56; #528 merged at 09:39 and added a Sender id toggle to #bar (76px). This PR adds #rx-cta (106px) plus a seventh callout. That is the same density that broke three onboarding.spec.js tests on the runner yesterday while passing on Windows, so the two changes land on exactly the shared resource.
I merged current master into this branch locally and ran it. It holds:
bar height 1024: 161 1280: 125 1440: 86
widths rx-cta 106 f-idclass-wrap 77 f-types 882
onboarding 17 passed
web 479 unit + 182 playwright passed
landing 35 passed
Local Windows metrics are the narrow ones though, and the runner's are what caught it last time, so please merge master in and let CI say so rather than taking my machine's word for it.
(landing.test.js failed for me at first on a missing fast-xml-parser -- my checkout, not this branch. Clean after npm ci.)
Checked, and each one holds
- "Inside the
<form>, so the focus trap includes it." Accurate:login.js:53istrapFocus($('login-form')), so the trap really is scoped to the form, andlogin.test.jsslicing betweenid="login-form"and</form>pins precisely the thing the claim rests on rather than merely the link's presence. - The guest notice. The diff adds a longer wording and then takes it back out, with the measurement recorded (972px against a row that holds ~800px). Net effect on the notice is nothing, which is the right outcome; worth saying out loud that the PR body's "goes back to" is the whole story and not a leftover.
- The responsive label.
.rx-cta-long/.rx-cta-shortswap ondisplay, which drops the hidden one out of the accessibility tree too, so a screen reader gets one label rather than "Start mappingMap it". Noaria-hiddenneeded. - One callout per control. Splitting
wb-co-mappingout rather than anchoring one box to the union ofrx-ctaandauth-btnis right, and the comment gives the reason plus the width it was found at. - Both link targets resolve:
rx.mesh-hunter.euandmesh-hunter.eureturn 200,rel="noopener"present on every newtarget="_blank".
Not a blocker, one thing left unsaid
The login card now tells a visitor to register in the RX webapp and that "an admin verifies you as a member afterwards". Nothing tells them how long that takes or how they would know it happened. Not this PR's job to build, but it is the next dead end on the same path, and worth an issue rather than being discovered by the next person who asks in Discord.
|
Filed the open end I mentioned as #530, so it is a decision rather than a discovery. Not a blocker for this PR. |
…through (#531) Closes #530 ## What was happening Two surfaces promise it -- `guestNotice('hunter')` and, since #491, the login card -- and the promise ended at a role change plus an audit row (`admin.go:133`). Nothing else happened. Worse, the role is read once at load (`login.js:49`, `map.js` `applyRole`). The person this is about is a hunter waiting to be verified, and what they do is leave the map open. Before this, that page kept saying "Hunter view" and drawing coarse hex however long ago an admin had acted, so the honest reading was that nothing had happened. ## Why the map has to tell them, not mail Mail was configured all along (`brevoSmtpHost`, `brevoSmtpPort`, `mailFrom` and the Brevo user are all set in the production config). Reach is the problem. From `users` on the live DB: | | | |---|---| | users | 16 | | with an email on file | 5 | | hunters waiting for member | 4 | | ...of those, with an address | **1** | `register` rejects a missing `companion_pubkey` but never checks `Email` before `CreateUser` (`auth.go:233`), so an address is optional and most people do not give one. Mail reaches one of the four people waiting today. It is the addition; the map is the mechanism. ## The map `web/rolechange.js` holds the comparison and stores the last-seen role in `localStorage`, the shape `whatsnew.js` already uses for its last-seen entry. No schema, no address. Two moments, one comparison: `applyRole` covers first load and login, and a `focus` listener re-reads `/api/auth/me` for the page that is already open. It re-applies only when the role actually moved -- `applyRole` re-runs the range and layer gates, and doing that on every tab switch is cost for nothing. - **Silent on a first visit.** With nothing stored, a promotion cannot be told from an arrival, and congratulating a member of six months on becoming one reads as a bug. The first load records; it does not announce. - **Silent on the way down.** A demotion is the admin's to explain, and the standing notice already changes to say what they can see. - The notice is `role="status" aria-live="polite"` and dismissible, and it is a separate element from `#guest-notice`: that one describes a standing state, this one is news. ## The mail `SendMemberVerified` joins the two methods on `Mailer`, sent synchronously like the invite mail beside it in the same file. The condition is **crossing** the member line, not merely rising. My first version was "rises and is at least member", and a test caught that it mails a member promoted to admin -- someone who has seen everything for months. ## Verified - `web/` 483 unit + 184 playwright; `server/` `go vet` + `go test ./...`; `app/` 782 (untouched, checked anyway); eslint clean. - Mutation-checked, three mutations, each killed: mail on any rise rather than on crossing (2 Go subtests), mail without an address (1 subtest), and announcing on a first visit (1 unit + 3 e2e). - The e2e drives the case that matters: role flips server-side while the page sits open, nothing is touched, and the notice appears while the standing notice goes. ## Note on the history `59b5da4` landed four of fourteen files: a `git add` with one bad pathspec stages nothing at all, and I pushed before noticing. `0d07ce4` completes it. Squash-merged the two are one commit; flagging it rather than force-pushing over it. --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
efiten
left a comment
There was a problem hiding this comment.
Approved. The three calls in here are the maintainer's, and all three are accepted (2026-08-26): the RX webapp leading over the map, "mapper" alongside "hunter" in the copy, and the account path spelled out.
Re-verified against current master, which has moved a long way since this branch's CI ran at 10:11 (#477, #500 and three releases). Merged master in locally and ran the lot:
web 510 unit, 193 playwright
landing 35
Onboarding included, so the extra bar control still costs no callout.
One thing said plainly rather than hidden: the first of my four playwright runs reported 1 failed / 192 passed, and I lost the output before identifying which test it was. The three runs after it were clean at 193. So it looks like a one-off flake rather than a fault in this branch, but CI runs with --fail-on-flaky-tests and is the arbiter, not my laptop. If it turns up there, it is that, not something new.
The code review from my earlier pass stands unchanged: the focus-trap claim holds (login.js:53 traps on #login-form, and the test slices exactly that), the guest-notice wording is added and then taken back out with the 972px measurement recorded, the responsive label swaps on display so a screen reader gets one label, and both link targets return 200 with rel="noopener".
The open end this leaves, that nothing tells a hunter when the verification actually comes through, is closed separately in #530 and shipped.
🤖 I have created a release *beep* *boop* --- <details><summary>app: 1.20.0</summary> ## [1.20.0](app-v1.19.1...app-v1.20.0) (2026-08-26) ### Features * **app:** search the target sheet by name or id prefix ([#477](#477)) ([c69c57c](c69c57c)) * **web,landing:** lead with mapping, and say where accounts come from ([#491](#491)) ([f9f326c](f9f326c)) * **web,server:** ignore a sender from the map ([#500](#500)) ([1b62809](1b62809)) </details> <details><summary>server: 1.8.0</summary> ## [1.8.0](server-v1.7.0...server-v1.8.0) (2026-08-26) ### Features * **web,server:** ignore a sender from the map ([#500](#500)) ([1b62809](1b62809)) </details> <details><summary>web: 1.16.0</summary> ## [1.16.0](web-v1.15.1...web-v1.16.0) (2026-08-26) ### Features * **app:** search the target sheet by name or id prefix ([#477](#477)) ([c69c57c](c69c57c)) * **web,landing:** lead with mapping, and say where accounts come from ([#491](#491)) ([f9f326c](f9f326c)) * **web,server:** ignore a sender from the map ([#500](#500)) ([1b62809](1b62809)) </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>
A visitor asked in Discord where to make an account for the map and could not find one. There is no answer to find: the login card offers login and nothing else, and the map cannot register anyone anyway, since
/api/auth/registerrejects a body without acompanion_pubkey(server/internal/httpapi/auth.go). Only the RX webapp has one.That gap turned out to be a positioning one. The RX webapp is the product: you pair a companion to it and map the mesh, and the map is where every mapper's receptions meet. Both sites led with the map instead, which sends a first-time visitor to a login screen for data they have no part in yet.
The map
#rx-cta, a Start mapping link in the bar next to Log in, opening rx.mesh-hunter.eu.<form>, so the focus trap includes it.The landing
Two measurements, because #bar wraps by content width
#rx-ctacosts no bar row at 1024/1280/1440. At 375px its long label does, so it shortens to "Map it" below the 640px breakpoint: that row holds ~80px, and "Start mapping" is 109px.Checks
npx vitest runinweb/(413),app/(716) andlanding/(35);npx playwright testinweb/(159); eslint clean.Closes #490