← all branches

feat/atlas-sat

needs attentionviewing older commit
744d655 · incrementalPR #353reviewed 2026-07-28 02:04 UTC0H · 1M · 5L · 8I
The branch
Purpose
Public atlas of Mexico's energy infrastructure (generation plants, transmission lines, interconnections) embedded as an iframe on batuenergy.com/atlas. Serves siting/market research for solar integrators.
Goal
Add satellite basemap toggle + searchable owner combobox to improve atlas usability
Sub-goals
  • SG-1: Satellite basemap toggle (Esri World Imagery with CARTO label overlay + 25% scrim)
  • SG-2: Searchable owner/group combobox replacing plain <select> (79 owners, 51 with 1 plant)
  • SG-3: Attribution control — moved from top-left to bottom-right, collapsed by default
The changes (whole branch)
What
Single file changed (apps/web/public/atlas/index.html, +87/-7 over the whole branch). Added: Esri raster source, basemap segment control, CARTO label preservation on satellite, scrim overlay, original-visibility restore on switch-back; replaced owner <select> with custom searchable combobox with keyboard nav and NFD normalization; attribution moved to bottom-right and collapsed on load.
Why
Satellite view gives substantive siting context (terrain, existing plants, industrial footprint) under grid layers. Owner filter was unusable with 79 entries and no search — 51 owners have a single plant so the tail was unreachable.
Areas
apps/web/public/atlas/index.html+877
Blast
1 file, +87/-7. No dependencies, no data files, no backend changes. Risk is confined to the atlas iframe.
static-asset public-facing no-backend-changes
typecheck· static HTML file — no TS compilationci· CI check query not available via this tokencoderabbit· no .coderabbit.yaml in repo

Findings · 13

correctness4

medium

Attribution collapse: querySelector may run before MapLibre applies compact-show class

apps/web/public/atlas/index.html

The block `{const _a=document.querySelector('.maplibregl-ctrl-attrib');if(_a)_a.classList.remove('maplibregl-compact-show');}` runs synchronously after addControl(). While addControl() inserts the control element synchronously, MapLibre may apply `maplibregl-compact-show` via a ResizeObserver callback (async). If so, the classList.remove fires before the class exists — the guard `if(_a)` masks the failure silently. A safer placement is inside a `map.once('load', …)` or as the first step of the existing _initFn callback. If this works today it's because the class is added synchronously on compact:true — but this is an undocumented timing dependency.

low

Enter with no highlighted item picks first owner (items[1]), not Todos (items[0])

apps/web/public/atlas/index.html

In onkeydown: `pick(hl>=0?hl:(items.length>1?1:0))`. When hl=-1 and multiple items exist, fallback is index 1 (first real owner) not index 0 (Todos/empty). A user who opens the list and immediately presses Enter expects to see 'Todos' selected (clearing the filter), not the top owner. Fallback should be 0.

low

NFD diacritic regex uses raw combining-char literal instead of \u0300-\u036f

apps/web/public/atlas/index.html

norm() uses `/[\u0300-\u036f]/g` written as invisible combining characters in the source literal. If the file is re-saved by an editor that normalizes or re-encodes the source, the range could silently break and diacritic stripping stops working. Prefer the explicit `/[\u0300-\u036f]/g` form.

info

Reset button does not close the combobox dropdown if open

apps/web/public/atlas/index.html

The reset onclick clears f_grp_in and f.own but does not call close(). If the dropdown is open when reset fires, it stays open showing the full unfiltered list.

security2

info

esc() escapes double-quotes only; latent risk if template switches to single-quoted attrs

apps/web/public/atlas/index.html

esc() escapes &, <, >, " but not '. Current templates use double-quoted HTML attributes, so this is safe. No active vulnerability, but a note for future template changes.

info

data-i converted with unary + without bounds check

apps/web/public/atlas/index.html

lst.onmousedown reads `+d.dataset.i`. data-i is only written by the render() function with the loop index (always a non-negative integer), so there is no injection path. Using parseInt(d.dataset.i, 10) with a bounds check would be more defensive but is not required.

conventions3

low

Combobox clear button is a <span> with no role, tabindex, or aria-label

apps/web/public/atlas/index.html

The `×` clear button (`<span class="cb-x">`) has only a `title="Limpiar"` tooltip. WAI-ARIA patterns require interactive controls to be reachable by keyboard and announced by screen readers. It should be `<button aria-label="Limpiar">` or at minimum `<span role="button" tabindex="0" aria-label="Limpiar">`.

low

aria-activedescendant not updated on keyboard navigation

apps/web/public/atlas/index.html

The combobox input has `role="combobox"` and `aria-controls="f_grp_list"` (listbox), but when ArrowDown/Up moves the highlight (hl), `aria-activedescendant` on the input is never set to point to the highlighted option's id. Screen readers won't announce the active item. Fix: add `id="f_grp_opt_N"` to each rendered option div and set `inp.setAttribute('aria-activedescendant', 'f_grp_opt_'+hl)` on navigation.

info

Future data sources without built-in attribution must be added back to customAttribution

apps/web/public/atlas/index.html

OSM/CARTO/Esri were correctly removed from customAttribution since MapLibre emits them from each source. But this is an implicit convention: any new data source added without a source-level attribution field must have its credit explicitly added to customAttribution to maintain legal compliance.

tests1

info

No automated tests for combobox keyboard navigation or NFD normalization

apps/web/public/atlas/index.html

Acceptable for a standalone static asset. If a Playwright harness ever covers apps/web/public, ArrowDown/Enter and 'garcia'→'García' matching are the highest-value cases to add.

improvement2

low

onfocus always opens with empty query, discarding display text context

apps/web/public/atlas/index.html

inp.onfocus calls open('') unconditionally. When an owner is already selected and the user refocuses, the full unfiltered list opens. Consider open(f.own||'') to pre-filter to the active selection, giving the user context about what's already selected.

info

norm() called on all 79 owners on every keystroke — could be cached

apps/web/public/atlas/index.html

Negligible perf impact at 79 items, but pre-computing `const ownersNorm = owners.map(norm)` once after owners is built makes the intent clearer and removes ~79 string allocations per keystroke.

seo1

info

Attribution now injected by map library — verify runtime rendering compliance

apps/web/public/atlas/index.html

OSM/CARTO/Esri credits removed from hardcoded HTML and now expected from MapLibre source attribution. File is an iframe embed (not crawlable), so SEO impact is nil. Worth confirming the attributions visually appear in the rendered ⓘ panel for legal compliance (OSM ODbL, Esri ToU).

History · 3 commits

  1. 55942absafeincremental0H · 1M · 4L2026-07-28 02:12
  2. 744d655needs attentionincremental0H · 1M · 5L2026-07-28 02:04current
  3. aec0922needs attentionfull2H · 3M · 2L2026-07-27 18:41