Review follow-up:
- Drop the Next.js route. CARTO_API_KEY is now a regular backend registry
key (env, .env, or the API Keys panel) served by public
GET /api/basemap-config. Every frontend mode already proxies /api/* to
the backend (Next.js proxy in web mode, companion server in packaged
desktop), so this covers web and desktop with one mechanism and leaves
the static export untouched. Also removes the invalid non-handler
export from the route module by removing the module.
- useBasemapConfig: fail open to the unkeyed style after 3 s, abort the
request at 15 s, apply a late key when it arrives, cache successes per
page and retry failures on the next mount.
- Declare OSM/CARTO attribution on the raster source (same markup as the
viewer's existing AttributionControl so MapLibre de-duplicates it).
- Tests: backend endpoint (unset / set+trimmed / persisted operator key /
registry), hook behaviour (success, non-OK, network error, soft timeout
then late key, hard abort, shared request and retry), attribution and
gating source checks.
- CARTO_API_KEY moves to the backend service in docker-compose.yml; docs
updated accordingly.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Tracked-flight and tracked-yacht HTML labels are rendered from the raw
store arrays, while their icons come from the worker GeoJSON that already
has the operator's data filters applied. Filtering Tracked Aircraft to a
category therefore removed the icons but left every other aircraft's name
on the map.
Derive the label subjects from the ids in the filtered feature collection
so a label is only drawn where an icon is. The id rule lives in one shared
helper used by both the worker builders and the label selector; rows with
no identifier keep the worker's positional fallback and draw no label.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
CARTO now requires an API key for its basemap tiles; without one every
tile in the DEFAULT dark/light map carries an "API KEY REQUIRED"
watermark. The tile URLs were hardcoded in mapStyles.ts with no way to
supply a key, and because the frontend ships as a prebuilt image a
NEXT_PUBLIC_ variable would be baked in empty for every Docker user.
- New frontend-local route GET /api/basemap-config reads CARTO_API_KEY
from the frontend container's environment at request time (same
pattern as BACKEND_URL), so no image rebuild is needed.
- useBasemapConfig() fetches it once per page load; MaplibreViewer
builds the MapLibre style from it via buildBasemapStyle(theme, key)
and defers the map's first style load until the config has settled,
avoiding a burst of unkeyed tile requests followed by a style swap.
- Tile URLs move to CARTO's documented rastertiles/ path with ?key=
appended when configured. Unkeyed URLs serve byte-identical tiles to
the old path, so deployments without a key behave exactly as before.
- CARTO_API_KEY wired through docker-compose.yml and documented in
.env.example, README (data source table + frontend env table) and
docs/OUTBOUND_DATA.md.
- Tests cover the route (unset / set / trimmed) and the style builder.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The vessel name list runs to tens of thousands of entries and every one
was mounted as a button when the dialog opened, so the Maritime dialog
took several seconds to appear. Because the dialog centres itself in an
effect after the first paint, that delay also showed it flashing at the
top-left before jumping to the middle.
Mount at most 300 rows until the search narrows the list, say how many
are hidden, and centre in a layout effect so the first paint is already
in place.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Carrier icons and labels are built on the main thread from the raw ship
array, separately from the worker that draws every other vessel, so the
Data Filters vessel name / type selection never reached them. Filtering
to yachts left the aircraft carriers on the map.
Move the ship filter predicate into a shared helper used by both the
worker and the carrier builder so all ship sources honour the same
selection.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Each Data Filters section acts on one or more map layers, but the panel
never knew the layer state, so a filter could target a layer that was
switched off and silently do nothing. Four of the five sections build
their option lists from live data, so with the layer off the dialog was
also empty and unsearchable, with no hint why.
Declare the layer keys per section, badge sections whose layers are all
off, and show a banner in the dialog with an ENABLE control per disabled
layer. Enabling from the dialog refetches within a few seconds and the
list fills in place. The empty list now says whether the layer is off,
data is still loading, or the search simply has no match. APPLY stays a
plain apply. Callers that do not pass layer state keep the old behaviour.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>