Skip to content

feat(region-filter): configurable named region quick-pick groups - #565

Merged
dborup merged 3 commits into
masterfrom
feat/416-region-quick-pick-groups
Oct 24, 2026
Merged

dborup merged 3 commits into
masterfrom
feat/416-region-quick-pick-groups

Conversation

@adminopenclaw8-sketch

@adminopenclaw8-sketch adminopenclaw8-sketch commented Oct 24, 2026 •

Copy link
Copy Markdown
Collaborator

Relates to #416.

Optional, bounded, operator-named groups of observer-region codes, rendered as quick picks in the shared region filter. No groups configured means byte-for-byte today's behaviour.

Plan

  1. Config — regionGroups: [{name, codes[]}] on Config, normalized once at load by normalizeRegionGroups, with the limits and the drop/normalize rule fixed and documented.
  2. API — serve the usable groups from a new GET /api/config/regions/groups, filtered per request against the same effective region map GET /api/config/regions returns.
  3. Frontend — fetch the groups alongside the regions, render one button per group, and route the selection through a single shared path with the region pills.
  4. CSS — themed through the existing variables, 48 px on touch devices, own full-width row at 390 px.
  5. Tests — Go for config normalization and the endpoint, a node unit suite against the real public/region-filter.js, and a Playwright suite on Nodes (pill mode), Packets (dropdown mode) and at 390 px with touch.

Per AGENTS.md rule 8: regionGroups is an operator-side config value and should be exposed in the customizer in a later milestone (it is a list of named code groups, so it needs a small repeater UI rather than a single field — out of scope here).

Why a sibling endpoint and not a field in /api/config/regions

That endpoint's response is a flat code → label object, and that is a documented contract: docs/api-spec.md says so, proto/config.proto models it as RegionsResponse, and every consumer (the region filter itself, the E2E suites that rewrite it) reads it as one. Adding a groups key would collide with the code namespace; switching to an envelope would break them all. The groups therefore get their own path under the same region config surface, asked for in parallel so they cost no extra round trip, with their own failure path. Considered and rejected: a ?groups=1 variant on the same path (response shape depending on a query parameter), and riding along in /api/config/client (which resolves long after the region filter initialises).

Limits and handling, explicitly

Case Behaviour
regionGroups absent or [] No quick picks, no log line; the filter renders exactly as before.
Name blank / over 40 characters Group dropped, one startup log line.
No codes listed Group dropped, one startup log line.
Over 24 distinct codes in a group Group dropped, one startup log line.
Over 12 groups Groups past the 12th dropped, one startup log line.
Repeated group name (case-insensitive) First one stands, later ones dropped.
Repeated code inside a group De-duplicated case-insensitively, first spelling kept.
Code no observer reports Dropped from the group; a group with no code left is not served at all.

The rule: a structural violation drops the whole entry; only trimming, de-duplication and unknown-code removal ever rewrite one. Serving a filter under a truncated code list, or under a shortened name, would mislabel what the operator asked for. Codes match case-insensitively (as the server does elsewhere) and come back in the spelling /api/config/regions uses, so a group's selection compares equal to one assembled by clicking pills.

Group names are convenience labels over region codes, not verified geographic boundaries — stated in the config comment, the API spec and the code.

One selection path

applySelection(next, container) is now the only place a new selection is saved, rendered and announced. toggleRegion (pills and dropdown) and the quick picks both call it, so:

  • a real change saves to localStorage, re-renders and notifies listeners once;
  • a no-op — picking the group already shown, or clicking All when all regions are already selected — re-renders (the dropdown's checkboxes have toggled themselves) but saves nothing and notifies nobody, so no listening page refetches. That closes the "duplicate refresh when clearing filters" half of the issue.

#531 / PR #549 is merged (ff1acc02) and this branch is synced onto it (merge 9063385a, one conflict in public/region-filter.js, resolved by hand). The result builds on its helper rather than beside it:

Both suites pass on the merge: test-issue-531-region-url.js 36/36, test-issue-531-region-url-e2e.js 28/28.

UI

Scope

cmd/server stays read-only (no DB writes, readonly_invariant_test.go unchanged). No new map[string]interface{} outside tests. Fork guards untouched (9 in deploy.yml, 1 in release-fast-path.yml). No staging or production change.

Relates to #416.

Operators can define optional named groups of observer-region codes in
config.json under `regionGroups`; the region filter renders each as one
button that selects that group's codes in a single action.

Server: `regionGroups` is normalized once at load (at most 12 groups, 24
distinct codes per group, 40-character names; trimming and case-insensitive
de-duplication applied, every other violation drops the entry with one log
line) and served by the new GET /api/config/regions/groups, filtered per
request to the codes /api/config/regions actually offers. The flat
code-to-label map that endpoint serves is a documented contract, so the
groups get their own path instead of a field inside it. No groups configured
serves [] and the filter behaves exactly as before.

Frontend: the quick picks go through one shared selection path with the
region pills (applySelection), so a real change saves, re-renders and
notifies listeners once, and a no-op — picking the group already shown, or
clearing an already-clear filter — notifies nobody and makes no page refetch.
They are buttons with aria-pressed, themed through the existing CSS
variables, 48 px on touch devices and a full-width row of their own at
390 px.
Relates to #416. The #333 DOM-level gate is where this component's escaping
is pinned, so the quick-pick label (text slot) and its codes (quoted title
attribute) get a case each, with a benign Danish name to catch a double
escape. loadRegionFilter now answers the groups endpoint separately.
…ck-pick-groups

# Conflicts:
#	public/region-filter.js
@adminopenclaw8-sketch

Copy link
Copy Markdown
Collaborator Author

Rapport — CS-Macmini PR#565 #416 — head 9063385

Status: Implemented, tested and synced onto the merged #531 work; all local suites green, 29 mutants killed, and every CI job green on the first attempt.

Evidence markers: [T] a test run in this session, [A] analysis of code/config, [K] known/pre-existing, verified against master.

Requirements

# Requirement Test (red before, green after) Mutant (killed)
1 Optional named groups in config, served by the existing region config surface, rendered as quick picks TestLoadConfigNormalizesRegionGroups, TestLoadConfigWithoutRegionGroups, TestHandleConfigRegionGroupsEndpoint; test-issue-416-region-quick-picks.js "no groups configured: no quick picks, control unchanged" [T] M11 LoadConfig normalization call removed; M12 route not registered; M24 quick-pick row never rendered [T]
2 No groups configured = exactly today's behaviour Endpoint serves [] with no regionGroups; E2E "the endpoint serves an empty list" + "no quick-pick row is rendered anywhere" against the real server (region pills count unchanged at 5) [T] M21 container always flagged has-region-quick-picks [T]
3 Limits defined explicitly (groups / codes per group / name length) TestRegionGroupLimitsAreTheDocumentedNumbers pins 12 / 24 / 40 as literals and asserts the 13th group, a 25-code group and a 41-character name behaviourally; TestNormalizeRegionGroupsLimits covers the at-cap cases M1 cap 12→100; M2 cap 24→100; M3 cap 40→400 [T]
4 Unknown codes TestRegionGroupsForRegions (unknown dropped, group with no known code omitted, case-insensitive match, served spelling); unit "codes the region list does not offer are dropped" + "a group with no known code is not offered" [T] M7 unknown codes served; M8 empty group still served; M9 case-sensitive match; M16 client-side filter removed [T]
5 Duplicate codes and duplicate names TestNormalizeRegionGroupsDuplicateCodes, TestNormalizeRegionGroupsDuplicateNames, .../repeats_do_not_count_against_the_code_cap; unit "codes match case-insensitively and take the served spelling" [T] M4 duplicate-name check disabled; M5 code de-duplication disabled [T]
6 Invalid config entries TestNormalizeRegionGroupsDropsInvalidEntries (5 shapes), TestNormalizeRegionGroupsAbsent; unit "malformed entries are dropped, usable ones kept", "a non-array groups response is ignored", "a failing groups request leaves the control working" [T] M6 empty-codes entry kept; M15 Array.isArray guard deleted; M22 groups-request catch removed; M10 nil slice instead of [] [T]
7 A quick pick goes through the same selection path — one notification on a real change Unit "a quick pick selects the group codes, saves and notifies once", "switching between two groups notifies once per change", "a pill toggle after a quick pick works off the picked set"; E2E "one click selects the group and refetches once with its codes" (asserts [["SJC","SFO"]]) [T] M23 toggleRegion bypasses applySelection; M14 all-codes group no longer normalizes to all regions [T]
8 No notification and no requests on a no-op Unit "picking the group already selected changes and announces nothing", "the same selection in another order is still a no-op", "an all-regions group picked while all are selected is a no-op", "clicking All while all regions are selected announces nothing", "an out-of-range or missing group index does nothing"; E2E "picking the same group again notifies nobody and fires no request", "All again, with nothing selected, announces nothing" [T] M13 applySelection no-op guard removed; M18 group index not validated [T]
9 URL state through #531/#549's helper Unit section "URL state, through #531's replaceState helper" (6 cases: parameter updated, other parameters and route kept, URL written before listeners are notified, all-regions removes it, hash without the parameter untouched, no-op writes nothing); E2E "a quick pick puts its codes in the URL, a reset takes them out" [T] M28 syncUrlRegion() dropped from applySelection — killed by both the #416 and the #531 suite [T]
10 Reset Unit "clicking All after a quick pick clears the selection once"; E2E "All resets the selection and announces it once" (one [null] notification, storage cleared, both groups un-pressed) [T] M13 (as above) [T]
11 Restored URL / local state Unit "a link selection lights the matching group"; E2E "the link selection lights the matching group" (#/packets?region=SJC,SFO) and "the selection and the active group survive a reload" (first fetch after reload carries region=SJC,SFO) [T] M14, M28 [T]
12 Keyboard accessible E2E "the keyboard operates a quick pick" (focus + Enter, one [["SFO"]] notification); unit "quick picks are buttons in a labelled group with aria-pressed" [T] M20 aria-pressed hardcoded false [T]
13 Touch accessible and works on mobile (390 px) E2E "the quick picks are visible and inside the viewport", "the quick picks form their own full-width row under the control", "each quick pick meets the 48 px touch minimum", "a tap selects the group and refetches with its codes" [T] M25 quick pick removed from the (pointer: coarse) 48 px block; M26 mobile full-width override removed (row falls back to an 8 px indent, 174 px wide); M27 container flex-wrap removed (region pill bar squeezed from 328 px to 178 px) [T]
14 Themed via CSS variables, no hardcoded colours git diff origin/master -- public/style.css has no hex literal; the active state reuses the --accent-strong / --text-on-accent pair Kpa-clawbot#1668 M5 settled on [A] M25–M27 (as above) [T]
15 Escaping of operator text and observer codes (#333) test-xss-escape-sinks.js gains "#416 region quick picks: hostile group name and code escaped in both slots" and "a benign group name is shown verbatim (no double escape)"; unit "a hostile group name and codes are escaped (#333)" [T] M19 label/title escaping removed (killed by the #416 suite); M19b same mutation killed by the escape-sink gate [T]
16 Dropdown mode and double-handling Unit "dropdown mode renders the quick picks too", "a dropdown checkbox click is not also handled as a quick pick"; E2E "quick picks render beside the dropdown trigger" [T] M17 pill branch runs in dropdown mode too [T]
17 Customizer follow-up noted (AGENTS.md rule 8) Documentation only, no test: the PR description records that regionGroups should reach the customizer in a later milestone, and why it needs a small repeater UI rather than a single field [A] —

Design decision recorded in the PR

The groups are served by a new GET /api/config/regions/groups rather than as a field inside GET /api/config/regions. That endpoint's flat code → label object is a documented contract (docs/api-spec.md, proto/config.proto RegionsResponse) and every consumer reads it as one; a groups key would collide with the code namespace and an envelope would break them all. TestHandleConfigRegionGroupsEndpoint/the_regions_endpoint_stays_a_flat_code_to_label_map guards that [T]. Alternatives considered and rejected: a ?groups=1 response-shape switch on the same path, and riding along in /api/config/client (which resolves long after the region filter initialises) [A].

The normalization rule is stated once and tested: a structural violation drops the whole entry; only trimming, case-insensitive de-duplication and unknown-code removal rewrite one. Serving a filter under a truncated code list, or a shortened name, would mislabel what the operator configured [A].

Sync with #531 / PR #549

#549 merged while this branch was in flight (ff1acc02). Synced immediately: merge 9063385a, one conflict in public/region-filter.js, resolved by hand so the result builds on #531's helper rather than beside it [A]:

Both #531 suites pass on the merge: test-issue-531-region-url.js 36/36, test-issue-531-region-url-e2e.js 28/28 [T].

Local runs

Suite Result
cmd/server go test ./... ok, 67.7 s [T]
cmd/ingestor, cmd/migrate, cmd/decrypt, cmd/anomaly, internal/* (5 modules) ok [T]
sh test-all.sh 267 passed, 0 failed (267 files) [T]
node test-frontend-helpers.js 719 passed, 0 failed [T]
node test-issue-416-region-quick-picks.js 34 passed, 0 failed [T]
test-issue-416-region-quick-picks-e2e.js 17 passed, 0 failed [T]
test-issue-531-region-url-e2e.js 28 passed, 0 failed [T]
test-xss-333-pages-e2e.js 11 passed, 0 failed [T]
test-issue-1136-live-region-e2e.js 5 passed, 0 failed [T]
test-issue-1224-channels-mobile-ux-e2e.js 16/16 [T]
test-xss-escape-sinks.js 122 passed, 0 failed (includes the two new #416 cases) [T]

E2E ran against a locally built server with a scratch copy of e2e-fixture.db prepared as the pipeline does (freshen, the grouped-row and GRP_DATA inserts, migrate, the four seed files). Without the grouped-row insert, test-xss-333-pages-e2e.js fails one case on fixture shape alone — that was a gap in my local preparation, not a code defect [T].

Red-before evidence: the Go tests failed to compile against master (undefined: RegionGroup), and the new E2E suite times out on master's frontend served from a git archive origin/master public copy (waiting for locator('#nodesRegionFilter .region-quick-picks')) [T].

Invariants

  • cmd/server stays read-only: no DB writes added, readonly_invariant_test.go unchanged and passing [T].
  • No new map[string]interface{} outside tests — git diff origin/master added none [A].
  • No hardcoded colours in the diff; scripts/check-xss-sinks.sh --diff origin/master exits 0 [T].
  • Fork guards unchanged: 9 in deploy.yml, 1 in release-fast-path.yml [T].
  • No staging or production access, no upstream writes, no server API key used [A].

CI per job

Run 40090896637, head 9063385a — every job green, first attempt, no re-runs [T]:

Job Result
📋 Verify Go Test Assignments pass
✅ Go Server Tests (1/3) pass
✅ Go Server Tests (2/3) pass
✅ Go Server Tests (3/3) pass
✅ Go Build & Test pass
✅ Go Ingestor Tests pass
✅ Go Libraries & CLI pass
✅ JavaScript Unit Tests & Lint pass
✅ Script & Container Unit Checks pass
🎭 Playwright E2E Tests (1/3) pass
🎭 Playwright E2E Tests (2/3) pass — the shard carrying test-issue-416-region-quick-picks-e2e.js
🎭 Playwright E2E Tests (3/3) pass
📊 Verify E2E Shards & Coverage pass
🧱 Validate Docker Image pass
📦 Release Artifacts skipped (fork, as designed)

The known-flaky #271 suite did not fail, so no re-run was needed [K].

Remainders

  1. Customizer — regionGroups is an operator-side config value and belongs in the customizer in a later milestone (AGENTS.md rule 8). It is a list of named code groups, so it needs a small repeater UI rather than one field; not filed [A].
  2. test-issue-2052-touch-target-e2e.js fails one case locally ("the add-channel dialog's close button … clipped by ch-layout by 32.4px"). Reproduced identically against master's frontend served from a git archive copy, so it is pre-existing and unrelated to the region filter; not filed [K].
  3. Shard timing — the new E2E suite is registered on shard 2 (the lowest measured total) with meanSeconds: 0 and a measurement note, like the other new suites. It takes about 35 s locally; the entry should be rebalanced once CI measures it [A].
  4. config.example.json now ships two example groups over the example regions block, so a fresh install copying the example gets working quick picks. The CI E2E server runs with no config.json, so existing suites see [] and are unaffected [A].
  5. /api/config/regions/groups is a second request at boot, fired in parallel with the regions request and cached per page load by the component's own _loaded flag. Rolling it into the regions response would have broken that endpoint's documented shape; noted here as the deliberate cost [A].

@dborup-agent

Copy link
Copy Markdown
Collaborator

Review — CS-pve-agent2 PR#565 — head 9063385

Dom: APPROVE with nits

Independent, read-only review. Evidence markers: [T] tested in this session, [A] analysis of code/config, [K] known/pre-existing, checked against master.

Basis: head 9063385a and the merged tree git merge-tree --write-tree origin/master 9063385a = 561e5ea6 against master 157e7831 (clean merge, no conflicts). Both came from git archive into scratch. The remote head was the same before and after the review [T].

Findings

# Severity Finding Evidence
1 Medium (scope, not blocking for a "Relates to" PR) RegionFilter.setSelected still saves and renders without notifying listeners. The issue's proposed work names this explicitly ("saves/renders programmatic setSelected changes without notifying page listeners, which must be addressed when wiring these controls"). The PR routes the quick picks around it through applySelection, which is fine. But neither the PR body nor the author's report records that the setSelected half is still open. Today it is latent: the only caller is Packets' Clear button, which calls loadPackets() itself. I measured exactly one /api/packets request with no region and zero onChange notifications after Clear following a quick pick. Pages subscribe one at a time, so no page shows stale data. Suggest recording it as remaining in #416, or a follow-up that routes setSelected through applySelection and drops Clear's explicit reload. [T] browser probe; [A] public/region-filter.js setSelected, public/packets.js Clear handler
2 Low (test gap) Surviving mutant: if render(container) is removed from the no-op branch of applySelection, every suite still passes: the #416 unit suite (34/34) and E2E (17/17), the #531 unit (36/36) and E2E (28/28), the Kpa-clawbot#1136 E2E (5/5) and test-frontend-helpers.js (719/719), with the E2Es run against a server serving the mutated public/. The re-render does matter. With the mutant, clicking All in the Packets dropdown while all regions are selected leaves the All checkbox unchecked; with the PR it stays checked. The code comment states exactly this reason, but no test pins it. One dropdown-mode unit case ("All on All keeps the All checkbox checked") would kill it. [T] mutant F + browser probe on both builds
3 Nit The E2E "one click selects the group and refetches once" asserts one notification and reqs.length > 0, not exactly one request. The comment explains why (app.js caches /api/nodes). My probes measured exactly one request per pick: one /api/nodes (390 px tap) and one /api/packets (desktop click). So there is no double request today, but the suite would not catch one. [T] probes; [A] test source
4 Nit / product question config.example.json now ships two active groups. docker/entrypoint.sh and manage.sh copy the example to config.json when none exists, so a fresh install gets quick picks by default (over the example SFO/SJC/OAK/MRY regions it already got). Deployments with an existing config are unaffected. The author's report notes this; mentioned so it is a deliberate choice. [A]
5 Info, pre-existing Back to an older history entry re-applies that entry's ?region= over a later choice made on another entry. Example: deep link #/packets?region=SJC, quick pick "Bay Area" (URL becomes SJC,SFO), go to Nodes, pick "Peninsula", then Back gives SJC,SFO and Forward keeps it. This is #556 item 1 (syncFromUrl, unchanged here); quick picks inherit it and do not make it worse. [T] probe; [K] #556

Answers to the review points

  • No groups configured = today's behaviour. The endpoint serves [] on the fixture server with no config [T]. With [], quickPicksHtml() returns '', so the markup is identical. The has-region-quick-picks class (the only CSS that changes layout) is never set. Pill-mode click handling is the same delegated container.onclick [A]. One intended difference: clicking All when all regions are already selected no longer saves, notifies or refetches. That is the issue's "duplicate refresh when clearing filters", and the case is red on master and green on the PR [T]. A new frontend against a server without the endpoint (master answers 404) falls back to [] through the request's own catch / normalizeGroups [T]/[A].
  • Config validation is explicit and tested. The caps are 12 groups, 24 distinct codes and 40 runes. Blank or over-long names, empty code lists and over-cap groups drop the whole entry with one log line. Names are de-duplicated case-insensitively (first wins) and codes are de-duplicated case-insensitively (first spelling). Unknown codes are dropped per request against the same effective map /api/config/regions serves, and a group left empty is omitted. Everything is documented in config.example.json, docs/api-spec.md, proto/config.proto and the code comments, and covered by region_groups_416_test.go [A]. My mutants B, C and D on these rules were all killed [T].
  • Same selection path as fix(region-filter): honor URL regions across views and clean up page listeners #549. toggleRegion (pills and dropdown) and pickGroup both end in applySelection. On a real change it saves, renders, calls syncUrlRegion() (the fix(region-filter): honor URL regions across views and clean up page listeners #549 helper, run before notify()) and notifies once. On a no-op it re-renders only [A]. A browser deep link #/packets?region=SJC plus quick pick gave URL region=SJC%2CSFO, one notification and one /api/packets?region=SJC,SFO request [T]. Reload keeps the selection and the pressed group; Back/Forward restore consistently, with region-filter: cross-entry Back, E2E API-count flake, history.state pin (follow-up to #549) #556's cross-entry caveat (finding 5) [T]. syncFromUrl and syncUrlRegion are unchanged [A].
  • UI. The quick picks are real <button type="button"> elements in a labelled role="group", with aria-pressed; the E2E covers keyboard Enter [T]. At 390 px with touch: no horizontal scroll (scrollWidth 390), the quick picks sit on their own row, each 48 px high, and one tap gives one request [T] (screenshot inspected). The active state uses --accent-strong / --text-on-accent, and no hex literal was added to CSS or JS [A].
  • E2E flakiness. test-issue-416-region-quick-picks-e2e.js passed 17/17 in 5 of 5 runs against a local Go server with the CI-prepared fixture [T].
  • Conflict with region-filter: cross-entry Back, E2E API-count flake, history.state pin (follow-up to #549) #556. region-filter: cross-entry Back, E2E API-count flake, history.state pin (follow-up to #549) #556 has no branch or PR yet, so a merge-tree check is not possible. Textually, feat(region-filter): configurable named region quick-pick groups #565 does not edit syncFromUrl or syncUrlRegion. It moves the syncUrlRegion() call from toggleRegion into applySelection and extracts regionsByUpper(). A region-filter: cross-entry Back, E2E API-count flake, history.state pin (follow-up to #549) #556 fix that adds history.state marking inside syncUrlRegion / syncFromUrl should merge cleanly. One that changes the call site would need to target applySelection [A].

Invariants

  • cmd/server stays read-only: no DB writes are added, the new handler only reads config plus GetDistinctIATAs(), and readonly_invariant_test.go is unchanged and passing [T]/[A].
  • New map[string]interface{} in the merged diff: 0 [T].
  • No hardcoded colours: the only #… hits in the added public/ lines are issue references [T]. scripts/check-xss-sinks.sh --diff origin/master exits 0 on the merged tree (561e5ea6) [T].
  • Fork guards are unchanged: 9 in deploy.yml, 1 in release-fast-path.yml, and .github/ is not touched [T].
  • No closing keywords in the PR body or commit messages [T]. All three commits have author and committer dborup <kontakt@meshview.dk> [T].

Tests (merged tree 561e5ea6 unless noted)

Suite Result
cmd/server go test ./... ok (79 s) [T]
sh test-all.sh 267 passed, 0 failed [T]
node test-frontend-helpers.js 719 passed, 0 failed [T]
node test-issue-416-region-quick-picks.js 34/34 [T]
node test-issue-531-region-url.js 36/36 [T]
node test-xss-escape-sinks.js 122/122 [T]
test-issue-416-region-quick-picks-e2e.js 17/17 ×5 [T]
test-issue-531-region-url-e2e.js 28/28 [T]
test-xss-333-pages-e2e.js 11/11 [T]
test-issue-1136-live-region-e2e.js 5/5 [T]
test-issue-1224-channels-mobile-ux-e2e.js 16/16 [T]
test-issue-2052-touch-target-e2e.js 19/19 [T]
test-e2e-playwright.js 26 pass, then fail-fast on "Version info lives on Perf dashboard" (#navStats wait). It fails identically against master's server and frontend with the same fixture, so it is local-environment/pre-existing and unrelated to this PR [K]

E2E server: a locally built Go server from the merged tree on a scratch copy of e2e-fixture.db, prepared like CI (freshen, the grouped-row/GRP_DATA SQL extracted from deploy.yml, corescope-migrate, the seeds 2073, 199, 245 and 122). Servers were stopped by port/pid [T].

Red before: the Go tests do not compile against master's config.go/routes.go (undefined: RegionGroup). The #416 unit suite against master's region-filter.js gives 9 passed, 25 failed; the 9 are baseline or vacuous no-op cases, and the author's M13 shows the no-op ones have teeth. The #416 E2E against a master server times out waiting for .region-quick-picks [T].

CI (run 40090896637, attempt 1): every job passes, including Go Server 1–3, Go Ingestor, Go Libraries & CLI, JS Unit & Lint, Playwright 1–3, the shard/coverage verify and Docker validate. Release/Publish/Deploy are skipped as designed on a fork PR [T]. The shard 2 log shows the #416 suite running and passing [T]. The known flakies #256 and #267 did not show up.

My own mutants:

Mutant Result
A — client normalizeGroups code de-duplication removed killed (unit: "codes match case-insensitively…") [T]
B — server name cap measured in bytes instead of runes killed (TestNormalizeRegionGroupsLimits) [T]
C — server group cap >= → > (13 groups kept) killed (…Limits, …AreTheDocumentedNumbers) [T]
D — server returns the configured spelling instead of the served one killed (TestRegionGroupsForRegions, endpoint test) [T]
F — render() removed from applySelection's no-op branch survived all suites, see finding 2 [T]

Not verified

@dborup
dborup marked this pull request as ready for review October 24, 2026 13:16
@dborup
dborup merged commit 5852595 into master Oct 24, 2026
18 checks passed
dborup added a commit that referenced this pull request Oct 24, 2026
Resolves the region-filter.js conflict with #565 (quick-pick groups)
semantically: noteChoice() lives in the shared funnel applySelection(),
so toggleRegion and pickGroup both count as the visitor's choice and a
quick pick stands on Back to an older entry's ?region=. noteChoice()
runs before the unchanged-selection early return, as toggleRegion did,
so an explicit choice that changes nothing still outranks an old link.

Adds an E2E (test-issue-531-region-url-e2e.js): quick-pick group on a
later entry, Back to the entry with the old ?region=, group preserved.
Red when noteChoice() is dropped from applySelection.

Relates to #556

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.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.

3 participants