Skip to content

feat(web): gate the point layer, and say what an account opens - #513

Merged
efiten merged 2 commits into
efiten:masterfrom
khagele:feat/493-hex-only
Aug 30, 2026
Merged

feat(web): gate the point layer, and say what an account opens#513
efiten merged 2 commits into
efiten:masterfrom
khagele:feat/493-hex-only

Conversation

@khagele

@khagele khagele commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

The layer control offered points, hex, both for every role. Below member
/api/points returns the last 24 hours and at most 500 rows
(server/internal/httpapi/degrade.go), so a guest switching to Points landed
on a thin or empty layer with nothing on the control saying why, and the
thinness read as a property of the data rather than of the account.

The hex heat is not windowed for anyone since #466, so the gate costs a
visitor nothing they could otherwise see.

The gate

auth.js gains three helpers beside canSeeLocate, which this mirrors:

canSeePointLayer, a member gate.
modeForRole, which holds a degraded role on hex whatever ?mode= asked
for. The mode is restored before /api/auth/me resolves, so it is
re-derived once the role is known, the same deferral ?locate=1 uses.
pointLayerReason, the line under the layer segments. Two audiences, the
split guestNotice already makes: a guest has no account, a hunter has one
and needs an admin to verify it, so "log in" is a dead end for them
(#174).

Since #539 the layer control is the #layer-seg segmented group in the
filter panel, so the gate lands there: Points and Both are disabled below
member, Hex stays live, and the reason renders as #layer-gate-note directly
under the group, with aria-describedby on the two disabled segments. A line
rather than a title, because on a phone a tooltip is nothing at all. The
segments stay visible rather than hidden: a visitor should be able to see
that the layer exists and what it takes to open it.

The seg click handler loses its syncTimeUi() call: the clamp note stopped
being layer-dependent when rangeLabelFor dropped showsPoints, so switching
layers no longer changes the range label.

A disabled attribute is not a disabled button

Nothing in style.css styled :disabled, so a gated segment rendered
identical to a live one, colour, opacity and cursor alike. #bar button:disabled at (1,1,1), because #bar select, #bar input, #bar button
sets colour at (1,0,1) and .bf-seg button.active sits at (0,2,1). e2e
asserts the rendered colour, opacity and cursor rather than the attribute.

Copy

The guest notice named "individual receptions" as a map layer, which is no
longer true for these roles: the ticker keeps its own /api/points feed and
is where they meet receptions. Rewritten to name it. Measured on the
current bar at 390x844 and 1280x720, the old and new strings render the
same bar heights for both roles, so the rewrite costs no room.

#492's clamp note moves with it: it named the point layer, which these
roles no longer have, so it names the ticker instead and no longer depends
on which layer is drawn.

A test that could not survive

e2e/auth.spec.js's "guest popup has no Locate button" opened a point popup
as a guest, which is now unreachable. It is split: a guest has no popup to
put a button in, and a member's popup still offers Locate, so the Locate
gate it was really about is still asserted.

Verified

web 509 unit, e2e 199/199 with --fail-on-flaky-tests, app 848 unit, eslint
clean, on ae9ba97. Mutation-checked on the segment build: dropping the
disable, dropping the mode re-derive, and emptying the note each turn
auth.spec red. The auth helper tests (gate boundary at hunter, per-role
copy, deep-link hold) carry over unchanged from the first round.

Closes #493

Co-Authored-By: Claude Opus 5 noreply@anthropic.com
Co-Authored-By: Claude Fable 5 noreply@anthropic.com

🤖 Generated with Claude Code

@khagele
khagele force-pushed the feat/493-hex-only branch 3 times, most recently from 50cdd66 to 0fb130a Compare August 25, 2026 19:10
@khagele
khagele force-pushed the feat/493-hex-only branch 3 times, most recently from 089e6ff to f81e96f Compare August 26, 2026 18:39
@efiten

efiten commented Aug 29, 2026

Copy link
Copy Markdown
Owner

The maintainer has taken the call (2026-08-29): yes, the point layer goes behind a member account. So the direction here is settled and it is worth finishing.

It cannot merge as it stands, and the conflict is not mechanical, which is why I left it rather than rebasing it for you.

This branch gates #layer-toggle, the cycling points/hex/both button. #541 replaced that with a segmented three-button group (#layer-seg), so the two sides of the conflict in map.js are two different controls:

this branch   document.getElementById('layer-toggle').addEventListener('click', ...)
master        for (const segBtn of document.querySelectorAll('#layer-seg button')) ...

canSeePointLayer and modeForRole carry over unchanged — they are about the role, not the widget. What needs rethinking is pointLayerReason, which was written as copy on the disabled button. With a segment group there is no single button to disable: presumably the points and both segments are the ones that go, and the reason has to live somewhere a reader will find it, which is a design question rather than a merge one. You own both halves, so it is yours to make rather than mine to guess.

The rest of your reasoning stands and I checked it: the hex heat is not windowed for anyone since #466, so a visitor loses nothing they could otherwise see, and the split copy for guest versus hunter is the right one given #174.

Ping me when it is pushed and I will review it properly.

The layer toggle cycled points, hex, both for every role. Below member
/api/points returns the last 24 hours and at most 500 rows
(server/internal/httpapi/degrade.go), so a guest pressing it landed on a
thin or empty layer with nothing on the control saying why, and the
thinness read as a property of the data rather than of the account.

The hex heat is not windowed for anyone since efiten#466, so the gate costs a
visitor nothing they could otherwise see.

auth.js gains three helpers beside canSeeLocate, which this mirrors:

  canSeePointLayer, a member gate.
  modeForRole, which holds a degraded role on hex whatever ?mode= asked
  for. The mode is restored before /api/auth/me resolves, so it is
  re-derived once the role is known, the same deferral ?locate=1 uses.
  pointLayerReason, the copy on the disabled button. Two audiences, the
  split guestNotice already makes: a guest has no account, a hunter has one
  and needs an admin to verify it, so "log in" is a dead end for them
  (efiten#174).

The button stays visible and disabled rather than hidden: a visitor should
be able to see that the layer exists and what it takes to open it.

Nothing in style.css styled :disabled, so the gated toggle rendered
identical to a live one, colour, opacity and cursor alike, and only the
tooltip said otherwise, which on a phone is nothing at all. Measured, then
fixed: `#bar button:disabled` at (1,1,1), because `#bar select, #bar input,
than the attribute.

The guest notice named "individual receptions" as a map layer, which is no
longer true for these roles: the ticker keeps its own /api/points feed and
is where they meet receptions. Rewritten to name it, and kept short on
purpose. Measured at 390x844 with a guest role, against master:

| bar | master | this branch |
|---|---|---|
| 390x844 guest | 321px | 321px |
| 1280x720 guest | 124px | 124px |
| 1280x720 hunter | 147px | 124px |

The first draft of that sentence was 9 characters over a line break and
cost 15px on a phone, which is where efiten#423 is fighting for room.

roles no longer have, so it names the ticker instead and no longer depends
on which layer is drawn.

e2e/auth.spec.js's "guest popup has no Locate button" opened a point popup
as a guest, which is now unreachable. It is split: a guest has no popup to
put a button in, and a member's popup still offers Locate, so the Locate
gate it was really about is still asserted.

433 unit tests, 171 e2e, eslint clean. Mutation-checked: making the gate a
hunter gate, dropping modeForRole, giving both roles the same copy, not
calling the gate from applyRole, disabling the button without re-deriving
the mode, dropping the reason from the title, and removing the :disabled
rule each turn the intended test red.

Stacked on efiten#512: this branch starts from it, since both change what applyRole
does and efiten#512's clamp note is the one being moved here.

Closes efiten#493

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@khagele
khagele force-pushed the feat/493-hex-only branch from f81e96f to 074218e Compare August 29, 2026 14:27
@khagele

khagele commented Aug 29, 2026

Copy link
Copy Markdown
Contributor Author

Pushed, 074218e, rebased onto ae9ba97.

The gate moved onto the segmented group. Points and Both are disabled below member, Hex stays live, and the reason renders as a line under the group (#layer-gate-note) with aria-describedby on the two disabled segments. A line rather than a title: on a phone a tooltip is nothing at all, which is the same reason the :disabled styling exists in this branch.

canSeePointLayer and modeForRole carry over unchanged, as you read them. pointLayerReason keeps both strings and its comment now says where they render.

One more resolution worth naming: the seg click handler on master calls syncTimeUi() because the clamp note was layer-dependent. This branch drops showsPoints from rangeLabelFor, so the note no longer depends on which layer is drawn and that call goes. The automerge kept it, which would have been a no-op call on every layer switch.

The bar-height table in the PR body was measured on the pre-#541 bar and no longer described anything, so it is replaced: measured on the current bar at 390x844 and 1280x720, the old and new guest and hunter strings render identical bar heights, so the copy rewrite costs no room.

Verification on the new top: web 509 unit, e2e 199/199 with --fail-on-flaky-tests (exit 0), app 848 unit, eslint clean. Three mutations on the segment build turn auth.spec.js red: dropping the disable, dropping the mode re-derive, emptying the note. CI is green.

@efiten efiten left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The gate reads well on the segmented control, which is the part that needed rethinking rather than rebasing. Points and Both disabled with Hex live is the right shape: a visitor can see the layer exists and that it is not theirs yet, which is more honest than hiding it and much more honest than the thin layer they used to get with nothing saying why.

Verified with master merged in: web/ 509 unit and 199 playwright, eslint clean.

Both halves of the gate are pinned, checked by breaking them:

  • The split copy. Collapsing the hunter branch so everyone gets the guest wording turns 3 unit tests red. That split is the whole of #174: telling a hunter to log in is a dead end, they are logged in, they are waiting on a person.
  • The reason actually reaching the reader. Disabling the segments without wiring aria-describedby turns an e2e red. Worth having as its own assertion, since a disabled control with an unreachable explanation is the failure mode this is meant to fix, and it is invisible to anyone testing with a mouse.

A line under the group rather than a title is the right call for the reason you give: on a phone there is no hover, so a title is not an explanation, it is a secret.

One thing I checked rather than assumed, since it is what makes this defensible: the hex heat has not been windowed for anyone since #466, so a visitor loses nothing they could otherwise see. The maintainer accepted that trade on 2026-08-29.

# Conflicts:
#	app/changelog.json
#	web/changelog.json
@efiten
efiten merged commit 71856f1 into efiten:master Aug 30, 2026
6 checks passed
@github-actions github-actions Bot mentioned this pull request Aug 30, 2026
efiten pushed a commit that referenced this pull request Aug 30, 2026
🤖 I have created a release *beep* *boop*
---


<details><summary>app: 1.24.0</summary>

##
[1.24.0](app-v1.23.0...app-v1.24.0)
(2026-08-30)


### Features

* **app:** give the theme three states and a memory, and end the
Settings tab ([#566](#566))
([e67c319](e67c319))
* **app:** keep the SNR the repeater heard us at
([#489](#489))
([0cd0a7c](0cd0a7c))
* **app:** make every recorded reception audible, one instrument per
type (+ sound tweaks)
([#470](#470))
([6244c0f](6244c0f))
* **web:** gate the point layer, and say what an account opens
([#513](#513))
([71856f1](71856f1))


### Bug Fixes

* **app:** take the gate's coach marks out of #splash's stacking context
([#565](#565))
([6fba59e](6fba59e))
</details>

<details><summary>web: 1.20.0</summary>

##
[1.20.0](web-v1.19.0...web-v1.20.0)
(2026-08-30)


### Features

* **app:** give the theme three states and a memory, and end the
Settings tab ([#566](#566))
([e67c319](e67c319))
* **app:** keep the SNR the repeater heard us at
([#489](#489))
([0cd0a7c](0cd0a7c))
* **app:** make every recorded reception audible, one instrument per
type (+ sound tweaks)
([#470](#470))
([6244c0f](6244c0f))
* **web:** gate the point layer, and say what an account opens
([#513](#513))
([71856f1](71856f1))


### Bug Fixes

* **app:** take the gate's coach marks out of #splash's stacking context
([#565](#565))
([6fba59e](6fba59e))
</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 layer toggle hands a guest a points layer the server clamps to 24 hours

2 participants