Skip to content

CLIM-1322 (3): Popup Title Display Discrepancy When InteractiveRegionOption.GRIDDED_DATA#684

Draft
renoirb-crim wants to merge 8 commits into
mainfrom
CLIM-1322_3_Popup-Title-Name-Resolution
Draft

CLIM-1322 (3): Popup Title Display Discrepancy When InteractiveRegionOption.GRIDDED_DATA#684
renoirb-crim wants to merge 8 commits into
mainfrom
CLIM-1322_3_Popup-Title-Name-Resolution

Conversation

@renoirb-crim
Copy link
Copy Markdown

@renoirb-crim renoirb-crim commented May 6, 2026

renoirb added 7 commits May 5, 2026 20:18
geocoder.all_areas has 648 distinct gen_term values and no categorization
column, so cdc_get_location_by_coords() and cdc_location_search() were
playing whack-a-mole with string filter lists that drifted apart over
time. Replace both functions' filtering with a shared tiered-preference
table (City/Town/Township > Village/Municipality/Hamlet > Borough; rest
falls to tier 99) plus a single hard-exclusion list, both living in
fw-child/resources/functions/gen-term-preferences.php so any future
adjustment is a one-file edit consumed by both endpoints.

cdc_get_location_by_coords() drops the preferred_terms two-pass and the
progressive-widening loop ($ranges = [0.05, 0.1, 0.2]) in favour of a
single bounding-box query (knob: \$bbox_range = 0.5, commented at the
call site) ordered by preference_tier ASC, distance ASC LIMIT 1. Folds
in CLIM-1322_2_'s Atom 1 (province_fr aliased AS province) so the same
fix lives in one place across branches.

Both functions now use mysqli_prepare + bind_param + stmt_get_result;
\$_GET['lat'], \$_GET['lng'], and \$_GET['q'] are bound rather than
interpolated. Response shapes preserved: location_search keeps {id,
text, term, location, province, lat, lon}; get_location_by_coords keeps
{geo_id, geo_name, generic_term, location, province, lat, lon, lng,
distance, coords, province_short, title} for parity with sibling Atom
1+2 in CLIM-1322_2_.
…Township to tier 2

Probes after Atom 10 surfaced two principled-but-undesired results: Toronto
resolved to York (Geographic Township) instead of Toronto (City), and Halifax
fell to Stewiacke because Metropolitan Area was absent from the tier list and
got dumped into the Tier 99 fallback. Re-tiers Township-class down to Tier 2,
and reinstates the legacy two-pass preferred_terms ('Metropolitan Area',
'Community') in Tier 1 so metro-class places (Halifax, Vancouver, Edmonton)
resolve correctly.
…alone for metros

Atom 11 promoted Community to Tier 1 alongside Metropolitan Area to fix the
Halifax-class metro case, but Community is double-purpose in geocoder.all_areas
— it serves both as a canonical city name (e.g. Toronto downtown) AND as
neighborhood names within larger cities. The neighborhood usage regressed
Vancouver to "West End, BC" (Community at 919m) beating "Vancouver, BC" (City
at 2.5km) — exactly the original Carrington-class wrong-name pathology this
ticket is fixing. Metropolitan Area alone in Tier 1 keeps Halifax fixed
without the Community bleed; Community now falls to the implicit Tier 99
fallback and only wins when no Tier-1/2/3 row exists in the bounding box.

Probe matrix — all 10 anchors return the canonical city/town:

 ┌─────────────────────────┬──────────────────┬─────────────────────┬──────────────────────────────────────────────────────────────────────────────────────────────────────┐
 │         Anchor          │     Returned     │ tier 1 generic_term │                                               distance                                               │
 ├─────────────────────────┼──────────────────┼─────────────────────┼──────────────────────────────────────────────────────────────────────────────────────────────────────┤
 │ Vancouver               │ Vancouver, BC    │ City                │                                                                                            2.5 km ✅ │
 ├─────────────────────────┼──────────────────┼─────────────────────┼──────────────────────────────────────────────────────────────────────────────────────────────────────┤
 │ McMasterville           │ Beloeil, QC      │ Ville               │ 3.2 km ✅ (closest Town per spec — UC-Search uses autocomplete row directly via Atom 9-equiv anyway) │
 ├─────────────────────────┼──────────────────┼─────────────────────┼──────────────────────────────────────────────────────────────────────────────────────────────────────┤
 │ Bedford                 │ Bedford, QC      │ Town                │                                                                                                 0 ✅ │
 ├─────────────────────────┼──────────────────┼─────────────────────┼──────────────────────────────────────────────────────────────────────────────────────────────────────┤
 │ Sainte-Adèle            │ Sainte-Adèle, QC │ Ville               │                                                                                                 0 ✅ │
 ├─────────────────────────┼──────────────────┼─────────────────────┼──────────────────────────────────────────────────────────────────────────────────────────────────────┤
 │ Montréal downtown       │ Montréal, QC     │ Town                │                                                                                            1.3 km ✅ │
 ├─────────────────────────┼──────────────────┼─────────────────────┼──────────────────────────────────────────────────────────────────────────────────────────────────────┤
 │ Edmonton                │ Edmonton, AB     │ City                │                                                                                            1.3 km ✅ │
 ├─────────────────────────┼──────────────────┼─────────────────────┼──────────────────────────────────────────────────────────────────────────────────────────────────────┤
 │ Calgary                 │ Calgary, AB      │ City                │                                                                                            1.0 km ✅ │
 └─────────────────────────┴──────────────────┴─────────────────────┴──────────────────────────────────────────────────────────────────────────────────────────────────────┘
…e province_short in autocomplete

Atom 9-equivalent's buildLocationTitle() produced the verbose autocomplete
dropdown display ("Montréal, (Ville), Montréal; Montréal, Québec") in the
popup title; make UC-Search's popup match the server-resolved short shape
("Montréal, QC") that cdc_get_location_by_coords already returns.

Server-side cdc_location_search() now adds province_short to each item,
derived via the existing short_province() helper from fw-child/functions.php
(already used by cdc_get_location_by_coords and cdc_get_location_by_id), so
the client doesn't need its own province-name mapping. buildLocationTitle()
is unchanged — leaflet-search still uses it as the dropdown display key.
…iants stay in tier 2

Clicking near Verchères returned L'Assomption because Tier-1 Town beat
Tier-2 Municipality regardless of distance. In Quebec — and broadly
across Canada — Ville and Municipalité are equivalent municipal types:
both are populated places governed by an elected council. Promote the
generic 'Municipality' to Tier 1; keep specialized variants
(Specialized / District / Rural / Parish / Resort / Mountain Resort /
Township / United Townships / Village / Northern Village / Cree Village
/ Naskapi Village Municipality) in Tier 2 since they signal
special-purpose context (parish = legacy religious admin, rural =
prairie units, resort = tourism zoning) that shouldn't outrank a
generic Municipality or Town nearby.
@renoirb
Copy link
Copy Markdown
Contributor

renoirb commented May 7, 2026

SQL query analysis

Between branches main and CLIM-1322_2_… and CLIM-1322_3_…

This is shared analysis context for review. The branches differ in how the two REST endpoints rank rows from geocoder.all_areas when resolving a click / locate-me / autocomplete request. Pass 2 (PR #682) unified the exclusion set but kept the distance-first strategy with a hard-coded blacklist. Pass 3 (PR #684) shifts to a tier-first strategy with a soft preference tier system, addressing the T28 vulnerability where a Public Park could out-rank a Town.

Endpoint ↔ PHP function mapping

WP REST path PHP function File:line
/wp-json/cdc/v2/get_location_by_coords/ cdc_get_location_by_coords fw-child/resources/functions/rest.php:330–335
/wp-json/cdc/v2/location_search/ cdc_location_search fw-child/resources/functions/rest.php:487–492

Route registrations (verbatim)

cdc_get_location_by_coords (lines 328–335):

add_action ( 'rest_api_init', function () {

	register_rest_route ( 'cdc/v2', '/get_location_by_coords/', array (
		'methods' => 'GET',
		'callback' => 'cdc_get_location_by_coords'
	) );

});

cdc_location_search (lines 485–492):

add_action ( 'rest_api_init', function () {

	register_rest_route ( 'cdc/v2', '/location_search/', array (
		'methods' => 'GET',
		'callback' => 'cdc_location_search'
	) );

});

1. cdc_get_location_by_coords() — SQL shape per branch

1.A main — Progressive widening + hard-coded preferred_terms list

Query logic:

  • Loop through three expanding ranges: [0.05, 0.1, 0.2] degrees.
  • For each range, fetch up to 50 rows with basic exclusions ('Railway Point', 'Railway Junction', 'Urban Community', 'Administrative Sector').
  • Post-filter in PHP: iterate rows and check if (in_array($row['generic_term'], $preferred_terms)) where $preferred_terms = ['Community', 'Metropolitan Area'].
  • On first match → return that row; if no match in any range → fall back to distance-only query at fixed 0.1° range.

SQL structure (illustrative — not prepared statement):

SELECT
  all_areas.id_code as geo_id,
  geo_name,
  gen_term[_fr] as generic_term,
  location,
  province[_fr] as province,
  lat,
  lon,
  DISTANCE_BETWEEN(lat_click, lng_click, lat, lon) as distance
FROM all_areas
[JOIN all_areas_sealevel ...]
WHERE lat BETWEEN (lat_rounded - range) AND (lat_rounded + range)
AND lon BETWEEN (lng_rounded - range) AND (lng_rounded + range)
AND gen_term NOT IN ('Railway Point', 'Railway Junction', 'Urban Community', 'Administrative Sector')
ORDER BY DISTANCE ASC
LIMIT 50

Key points:

  • $ranges = [0.05, 0.1, 0.2] — three queries, not one.
  • Returns on first in_array() hit, so order of loop, not SQL, drives the choice.
  • Falls back if no preferred_terms found.

1.B CLIM-1322_2_… (PR #682) — Single query, unified exclusions, distance-only ranking

Query logic:

  • Single nearest-neighbour lookup at fixed 0.2° range.
  • Hard-coded exclusion set: 25 terms (all admin/region/county types).
  • ORDER BY distance ASC, LIMIT 1 — nearest wins.

SQL (prepared statement, lines 53–77):

SELECT
  all_areas.id_code as geo_id,
  geo_name,
  gen_term[_fr] as generic_term,
  location,
  province[_fr] as province,
  lat,
  lon,
  DISTANCE_BETWEEN(lat_click, lng_click, lat, lon) as distance
FROM all_areas
[JOIN all_areas_sealevel ...]
WHERE lat BETWEEN (lat_rounded - 0.2) AND (lat_rounded + 0.2)
AND lon BETWEEN (lng_rounded - 0.2) AND (lng_rounded + 0.2)
AND gen_term NOT IN (
  'Administrative Region', 'Administrative Sector',
  'Census Division', 'Census Subdivision',
  'County', 'County Municipality', 'County Regional Municipality',
  'District Municipality',
  'Improvement District', 'Local Government District',
  'Local Service District', 'Local Urban District',
  'Municipal County', 'Municipal District',
  'Municipality', 'Province', 'Region',
  'Regional District', 'Regional Municipality',
  'Restructured County',
  'Specialized Municipality',
  'Subdivision', 'Territory', 'Unorganized Territory'
)
ORDER BY distance ASC
LIMIT 1

Key points:

  • Single query.
  • Hard exclusion of 25 terms (no soft ranking).
  • Vulnerable to T28 Public Park beating Town (T2) at higher distance because both pass the exclusion filter.

1.C CLIM-1322_3_… (PR #684, current) — Single query, tier-first ranking, prepared statement

Query logic:

  • Single nearest-neighbour lookup at fixed 0.5° range (wider to ensure sparse-area coverage with soft filtering).
  • Tier-based preference: CASE WHEN gen_term IN (Tier 1 terms) THEN 1 WHEN ... THEN 2 WHEN ... THEN 3 ELSE 99 END.
  • Hard exclusion: 24 terms (hard-coded list, identical to gen-term-preferences.php).
  • ORDER BY preference_tier ASC, distance ASCtier first, then distance tiebreak.

SQL (prepared statement, lines 395–411):

SELECT
  all_areas.id_code as geo_id,
  geo_name,
  gen_term[_fr] as generic_term,
  location,
  province[_fr] as province,
  lat,
  lon,
  DISTANCE_BETWEEN(?/*lat_click*/, ?/*lng_click*/, lat, lon) as distance,
  CASE
    WHEN gen_term IN (
      ?/*City*/, ?/*Town*/, ?/*Separated Town*/,
      ?/*Metropolitan Area*/, ?/*Municipality*/
    ) THEN 1
    WHEN gen_term IN (
      ?/*Township*/, ?/*Township Municipality*/, ..., ?/*Townsite*/
    ) THEN 2
    WHEN gen_term IN (
      ?/*Borough*/
    ) THEN 3
    ELSE 99
  END as preference_tier
FROM all_areas
[JOIN all_areas_sealevel ...]
WHERE lat BETWEEN ?/*lat_r - 0.5*/ AND ?/*lat_r + 0.5*/
AND lon BETWEEN ?/*lng_r - 0.5*/ AND ?/*lng_r + 0.5*/
AND gen_term NOT IN (
  ?/*Administrative Region*/, ?/*Province*/, ?/*Territory*/,
  ?/*Census Division*/, ?/*Census Subdivision*/,
  ?/*Regional Municipality*/, ?/*County Regional Municipality*/,
  ?/*County Municipality*/, ?/*Municipal County*/, ?/*Municipal District*/,
  ?/*Region*/, ?/*Regional District*/, ?/*Restructured County*/,
  ?/*County*/, ?/*Improvement District*/, ?/*Local Government District*/,
  ?/*Local Service District*/, ?/*Local Urban District*/,
  ?/*Subdivision*/, ?/*Unorganized Territory*/,
  ?/*Urban Community*/, ?/*Administrative Sector*/,
  ?/*Railway Point*/, ?/*Railway Junction*/
)
ORDER BY preference_tier ASC, distance ASC
LIMIT 1

Key points:

  • Single query, prepared statement with placeholders.
  • Wider bbox (0.5° vs 0.2°) — preference tiers do the fine-grained filtering.
  • Tier 1 (5 terms): City, Town, Separated Town, Metropolitan Area, Municipality.
  • Tier 2 (30 terms): Township variants, Village variants, specialized Municipality types, Hamlet variants.
  • Tier 3 (1 term): Borough.
  • Implicit Tier 99: anything not in Tiers 1–3 (e.g., Community, parks, etc.).
  • Hard exclusion: 24 terms (admin/region/county types, plus Railway/Urban Community/Admin Sector).
  • ORDER BY preference_tier, distance ensures a Tier 1 Town always beats a Tier 99 park, even at double the distance.

2. cdc_location_search() — SQL shape per branch

2.A main — Basic WHERE + ORDER BY scale

SQL (excerpt):

SELECT SQL_CALC_FOUND_ROWS
  id_code, geo_name, gen_term[_fr], location, province[_fr], lat, lon
FROM all_areas
WHERE gen_term NOT IN ('Administrative Region', 'Province', 'Territory')
  AND geo_name LIKE '%<search_term>%'
ORDER BY scale DESC
[LIMIT 0,10 if query < 5 chars]

Key points:

  • Three-term exclusion: only admin regions.
  • No prepared statement (string concatenation — SQL injection risk).
  • Ranks by scale DESC (larger areas first) — not distance or tier.
  • Does not inherit preferred_terms logic from cdc_get_location_by_coords().

2.B CLIM-1322_2_… — Unchanged from main

Same as 2.A. Pass 2 did not touch cdc_location_search().


2.C CLIM-1322_3_… — Prepared statement, tier-first ranking

SQL (prepared statement, lines 541–554):

SELECT SQL_CALC_FOUND_ROWS
  id_code,
  geo_name,
  gen_term[_fr],
  location,
  province[_fr],
  lat,
  lon,
  CASE
    WHEN gen_term IN (
      ?/*City*/, ?/*Town*/, ?/*Separated Town*/,
      ?/*Metropolitan Area*/, ?/*Municipality*/
    ) THEN 1
    WHEN gen_term IN (
      ?/*Township*/, ..., ?/*Townsite*/
    ) THEN 2
    WHEN gen_term IN (
      ?/*Borough*/
    ) THEN 3
    ELSE 99
  END as preference_tier
FROM all_areas
WHERE gen_term NOT IN (
  ?/*Administrative Region*/, ?/*Province*/, ?/*Territory*/,
  ?/*Census Division*/, ?/*Census Subdivision*/,
  ?/*Regional Municipality*/, ?/*County Regional Municipality*/,
  ?/*County Municipality*/, ?/*Municipal County*/, ?/*Municipal District*/,
  ?/*Region*/, ?/*Regional District*/, ?/*Restructured County*/,
  ?/*County*/, ?/*Improvement District*/, ?/*Local Government District*/,
  ?/*Local Service District*/, ?/*Local Urban District*/,
  ?/*Subdivision*/, ?/*Unorganized Territory*/,
  ?/*Urban Community*/, ?/*Administrative Sector*/,
  ?/*Railway Point*/, ?/*Railway Junction*/
)
AND geo_name LIKE ?
ORDER BY preference_tier ASC, scale DESC
[LIMIT 0,10 if query < 5 chars]

Key points:

  • Prepared statement with parameterized LIKE binding.
  • Harmonized exclusion set with cdc_get_location_by_coords() (24 terms).
  • Tier-first ranking: ORDER BY preference_tier ASC, scale DESC — Tier 1 places appear before Tier 2, even if smaller.
  • Inheritance: both endpoints now use gen_term_preference_tiers and gen_term_excluded from gen-term-preferences.php.
  • scale DESC remains for within-tier ordering (larger places first, at same tier and distance).

3. Side-by-side summary

Aspect main CLIM-1322_2 CLIM-1322_3
cdc_get_location_by_coords: passes 3 queries (ranges [0.05, 0.1, 0.2]) 1 query (0.2°) 1 query (0.5°)
Filter style Post-filter in PHP (in_array) Hard exclusion (25 terms) Hard exclusion (24 terms)
Ranking in_array match on ['Community', 'Metropolitan Area'], fallback to distance distance ASC preference_tier ASC, distance ASC
Prepared statement ✗ (string concat) ✗ (string concat)
Returns province_short
Tier-aware ✗ (hard-coded list) ✓ (Tiers 1–3 + Tier 99 implicit)
cdc_location_search: exclusion set 3 terms 3 terms 24 terms (harmonized with coords)
Ranking scale DESC scale DESC preference_tier ASC, scale DESC
Prepared statement
Tier-aware

4. Behavioral consequences — why _2 and _3 produce different popup titles

The Saint-Anthony-of-Padua (Montreal centroid) case:

  • Click at 45.5°N, 73.5°W (Montreal city center).
  • Within 0.5° bbox:
    • Town "Outremont" at ~900m.
    • Public Park "Mount Royal" at ~1.5km.
    • Borough "Saint-Louis" (arrondissement) at ~2km.

Pass 2 behavior (distance-first):

  • Nearest wins: Outremont (Town, ~900m) returned.
  • Parks are excluded, so Mount Royal doesn't enter.
  • ✓ Correct for this case.

But with Public Parks in an excluded list (NOT IN), the system is fragile:

  • T28 vulnerability: An unknown park at 800m beats a known Town at 900m if the park's gen_term is not in the exclusion list.
  • The exclusion list (Pass 2) has 25 hardcoded terms; new gen_term values (e.g., from data updates) can slip through.
  • Example: If geocoder adds "Nature Preserve" as a gen_term, it passes the exclusion check and beats any Town by distance alone.

Pass 3 behavior (tier-first):

  • Nearest wins within each tier: Tier 1 (Town, Outremont) returned.
  • Mount Royal (Tier 99, implicit fallback — park gen_term not in Tiers 1–3) is excluded from the final query because it's in the hard exclusion list (gen_term NOT IN (...)).
  • But even if a park were in-scope and Tier 99, Outremont (Tier 1) would win regardless of distance.
  • ✓ Correct, and resilient to data updates: any new gen_term falls into Tier 99, so Tier 1–3 always win.

Key difference:

  • Pass 2: Distance-first strategy with a hard-coded blacklist → vulnerable to unknown gen_terms.
  • Pass 3: Tier-first strategy with a soft preference system → unknown gen_terms default to Tier 99 and lose to Tier 1–3 by default.

5. Use-case scenarios — what each Pass does for each endpoint

Use case Path main CLIM-1322_2 CLIM-1322_3
UC-Search (autocomplete) location_search scale DESC scale DESC preference_tier ASC, then scale DESC
Behavior Larger places first, no tier awareness. Larger places first, no tier awareness. Tier 1 (City/Town) always appears before Tier 2 (Village), even if smaller.
UC-LocateMe (browser geoloc) get_location_by_coords 3 queries, preferred_terms in PHP 1 query, distance-only ranking 1 query, tier-first ranking
Behavior Community/Metro in preferred_terms; fallback to nearest. Nearest non-admin place. Nearest Tier 1 place (City/Town); Tier 2 as fallback.
UC-MapClick (map interaction) get_location_by_coords Same as LocateMe Same as LocateMe Same as LocateMe
UC-RawCoordPaste (manual coords) get_location_by_coords Same as LocateMe Same as LocateMe Same as LocateMe
UC-PolygonCENSUS (Shapefile mode) Not affected
UC-PolygonHEALTH (Health region) Not affected
UC-PolygonWATERSHED (Watershed) Not affected

Notes:

  • All polygon modes (CENSUS, HEALTH, WATERSHED) are unchanged across passes.
  • The ranking changes only affect the fallback nearest-place name when no polygon is selected.
  • Pass 3 search and location-by-coords are aligned in exclusion and tier rules (CLIM-1322 requirement) — they now return the same name for the same place.

Appendix: Tier definitions (from gen-term-preferences.php)

Tier 1 (5 terms — major settlements with population centers):

  • City, Town, Separated Town, Metropolitan Area, Municipality (generic)

Tier 2 (30 terms — smaller settlements and special-purpose municipalities):

  • Township, Township Municipality, Geographic Township, United Townships Municipality
  • Village, Village Municipality, Northern Village, Northern Village Municipality
  • Resort Village, Summer Village, Rural Village, Police Village, Forest Village, Provincial Historic Village
  • Cree Village, Cree Village Municipality, Naskapi Village, Naskapi Village Municipality
  • First Nation Village, Former First Nation Village
  • Specialized Municipality, District Municipality, Rural Municipality, Parish Municipality
  • Resort Municipality, Mountain Resort Municipality
  • Hamlet, Northern Hamlet, Organized Hamlet
  • Townsite

Tier 3 (1 term — borough / arrondissement):

  • Borough

Implicit Tier 99 (fallback — anything not in Tiers 1–3):

  • Community (neighborhoods within cities, not canonical places)
  • Parks, lakes, cemeteries, points-of-interest
  • Any new gen_term values from data updates

Hard exclusions (24 terms — never returned, regardless of tier):

  • Administrative Region, Province, Territory
  • Census Division, Census Subdivision
  • Regional Municipality, County Regional Municipality, County Municipality, Municipal County, Municipal District
  • Region, Regional District, Restructured County, County
  • Improvement District, Local Government District, Local Service District, Local Urban District
  • Subdivision, Unorganized Territory
  • Urban Community, Administrative Sector
  • Railway Point, Railway Junction

Analysis complete. Prepared for colleague review.

WHY: Renoir reading the data-driven SQL-fragment helper found the
placeholder/bind-type/value arrays hard to follow on first read. The
existing helper docblock described outputs abstractly without showing the
input -> output shape with concrete values, and the call-site array_merge
sequences had no reminder that order is positionally tied to $types.

Add a worked-example block to the helper docblock (showing actual gen_term
strings flowing into case_values, case_types, etc), one inline comment in
the foreach loop explaining what each iteration emits, and a two-line
"order MUST mirror $types" reminder above each array_merge call site in
rest.php. No logic changes.
renoirb added a commit that referenced this pull request May 19, 2026
Edited: 2026-05-19

This branch was originally written BEFORE creating `dispatchMapClick` and has been
now merged to main in PR #676. During the rebase that followed 676 the important
aspect of `CLIM-1322_Popup-Title-Name-Resolution` was to pass the title.

Passing the title was an illustrative "band aid" and we've kept this branch
to return back to it later.

After Passes 2 supplemental passes (...):
- #682 branch `CLIM-1322_2_Popup-Title-Name-Resolution`
- #684 branch `CLIM-1322_3_Popup-Title-Name-Resolution`

We've decided to return to what this branch initially suggested.
Simply to pass the title from the click handler.

This commit is there to illustrate what was lost during the iterations to set the record straight.

Commits that had gotten lost and were part of this branch this commit is replacing:
- 72a0df4
- f9169bb

Earlier commit message:

Map search popup was showing neighboring Community names (e.g. "Toronto" →
"North York") because handleLocationChange fired a synthetic click that
discarded the search item's pre-resolved title, forcing handleClick to
re-derive from lat/lng via fetchLocationByCoords, which snaps to the
nearest 'Community' / 'Metropolitan Area' and filters out 'City' entries
server-side.

D1a from the Analysis Plan: plumb buildLocationTitle(item) through
handleLocationChange into the synthetic click payload as an optional
searchProvidedTitle field. handleClick prefers it over fetchLocationByCoords
when the mode is GRIDDED_DATA, skipping the network call on the happy path.

This is the third optional field on the synthetic click payload (joining
layer.properties from CLIM-1223). Submitted as a draft PR for team
discussion on whether to continue the bandaid pattern or escalate to the
structural refactor in D1c. See [[LLM-Context-ClimateData-Ticket-CLIM-1322-Analysis-Plan]]
for the full design trade-off.
renoirb added a commit that referenced this pull request May 19, 2026
Edited: 2026-05-19

This branch was originally written BEFORE creating `dispatchMapClick` and has been
now merged to main in PR #676. During the rebase that followed 676 the important
aspect of `CLIM-1322_Popup-Title-Name-Resolution` was to pass the title.

Passing the title was an illustrative "band aid" and we've kept this branch
to return back to it later.

After Passes 2 supplemental passes (...):
- #682 branch `CLIM-1322_2_Popup-Title-Name-Resolution`
- #684 branch `CLIM-1322_3_Popup-Title-Name-Resolution`

We've decided to return to what this branch initially suggested.
Simply to pass the title from the click handler.

This commit is there to illustrate what was lost during the iterations to set the record straight.

Commits that had gotten lost and were part of this branch this commit is replacing:
- 72a0df4
- f9169bb

Earlier commit message:

Map search popup was showing neighboring Community names (e.g. "Toronto" →
"North York") because handleLocationChange fired a synthetic click that
discarded the search item's pre-resolved title, forcing handleClick to
re-derive from lat/lng via fetchLocationByCoords, which snaps to the
nearest 'Community' / 'Metropolitan Area' and filters out 'City' entries
server-side.

D1a from the Analysis Plan: plumb buildLocationTitle(item) through
handleLocationChange into the synthetic click payload as an optional
searchProvidedTitle field. handleClick prefers it over fetchLocationByCoords
when the mode is GRIDDED_DATA, skipping the network call on the happy path.

This is the third optional field on the synthetic click payload (joining
layer.properties from CLIM-1223). Submitted as a draft PR for team
discussion on whether to continue the bandaid pattern or escalate to the
structural refactor in D1c. See [[LLM-Context-ClimateData-Ticket-CLIM-1322-Analysis-Plan]]
for the full design trade-off.
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.

2 participants