Layer rename, Load button border fix, and Add-Vector-Layer auto refresh¶
Date: 2026-06-11
Summary¶
Three independent changes, two repos:
- Rename layers in the GeoLibre Layers panel (GeoLibre only).
- Fix the clipped "Load" button focus ring in the Add Vector Layer panel (GeoLibre only).
- Enable Auto Refresh for Add-Vector-Layer GeoJSON-URL layers via a new upstream
reloadLayer()API (maplibre-gl-vector + GeoLibre).
Parts 1 and 2 ship together in a GeoLibre PR. Part 3 is upstream-first: add and release reloadLayer() in maplibre-gl-vector, then bump the dependency and wire it in GeoLibre.
Part 1: Rename layers¶
Goal¶
Let users rename a layer's display label in the Layers panel.
Background¶
GeoLibreLayer.name(packages/core/src/types.ts) is a pure display label. MapLibre sync keys offlayer.id, nevername, so a rename cannot break rendering (packages/map/src/layer-sync.ts,map-controller.ts).- The store already has a generic
updateLayer(id, patch)action (packages/core/src/store.ts), so renaming isupdateLayer(layer.id, { name }). No new store action.
Design (apps/geolibre-desktop/src/components/panels/LayerPanel.tsx)¶
- Track edit state with component state:
editingLayerId: string | nullandeditingName: string. - The layer name
<span>(currentlyLayerPanel.tsx:610) renders as a text<input>wheneditingLayerId === layer.id, otherwise as the existing span. - Two entry triggers (per user's choice):
- Double-click the name span enters edit mode.
- A new Rename
DropdownMenuItem(with aPencilicon) added at the top of the per-layer...menu (before "Materialize"/"Refresh"). - On entering edit mode the input auto-focuses and selects all text.
- Commit: Enter key or input blur calls
updateLayer(layer.id, { name: trimmed }). No-op when the value is empty or unchanged. Escape cancels without committing. stopPropagationon the input's mouse/key events so editing does not toggle layer selection or trigger drag.- Applies only to real
GeoLibreLayerentries, not the synthetic "Background" basemap row.
Testing¶
- A
tests/*.test.tsfrontend test assertingupdateLayerrenames and that sync/MapLibre is unaffected (name is not used as a key). Manual verification in the running app for the double-click and menu paths, including Escape-cancel and empty-name no-op.
Part 2: Load button border clipping¶
Goal¶
The "Load" button's focus ring in the Add Vector Layer panel is clipped on its right edge. Show the full ring.
Root cause¶
- The button is the upstream
maplibre-gl-vector.vector-control-button. Its:focusstyle isoutline: 2px solid; outline-offset: 2px(upstreammaplibre-gl-vector.css). - The button sits flush against the right edge of
.vector-control-content, which hasoverflow-y: auto. Per CSS, that forcesoverflow-xto a clipping value, so the offset focus ring is cut off on the right.
Design (apps/geolibre-desktop/src/index.css)¶
- Add a scoped override under
.geolibre-vector-panel(never editnode_modules, per repo convention) that gives the focus ring room so it is not clipped. Preferred approach: a few px ofpadding-righton.vector-control-content(and matching left padding to stay symmetric), or reduce the button'soutline-offsetso the ring stays inside the clip box. - Choose whichever reads cleanest in the running app and verify visually in both light and dark themes.
Testing¶
- Visual verification in the running web app: focus/click the Load button and confirm the full ring renders, including the right edge, in light and dark mode.
Part 3: Auto Refresh for Add-Vector-Layer GeoJSON-URL layers¶
Goal¶
A GeoJSON layer added through the Add Vector Layer panel via an HTTP(S) URL should support manual Refresh and Auto Refresh, matching WFS layers.
Background / why it is currently excluded¶
- WFS layers are added through
addGeoJsonLayerand are store-rendered GeoJSON; the existing refresh path re-fetches and callsupdateLayer({ geojson })(apps/geolibre-desktop/src/lib/layer-refresh.ts). - Add-Vector-Layer URL layers are created by the external maplibre-gl-vector control, which owns native MapLibre sources/layers. Their store layer is tagged
externalNativeLayer: trueandsourceKind: "maplibre-gl-vector"(packages/plugins/src/plugins/vector-layer-sync.ts: createVectorStoreLayer). refreshSourceUrlreturnsnullwhenexternalNativeLayer === true(layer-refresh.ts:188), so these layers are never refreshable. The store-geojson refresh path also cannot drive them, because the control renders the data, not the store.- The control exposes only
addData()andremoveLayer()(no in-place reload). Remove + re-add would mint a new layer id each refresh, breaking selection, ordering, and the auto-refresh timer config. Hence the upstream-first approach.
Upstream change (maplibre-gl-vector)¶
Add an in-place reload that re-fetches a URL-backed layer while preserving its id, source id, style, render mode, and position. Model it on the existing in-place setRenderMode() re-render (src/lib/core/LayerManager.ts).
LayerManager.reloadLayer(id):
1. Look up the record; return undefined if missing. If record.source is not a URL string, return the current info unchanged (files/in-memory GeoJSON are static).
2. Emit loading (Refreshing <name>...).
3. Tear down current presentation: _detachPicker, removeLayersAndSource(map, layerIds, sourceId), unregister the tile provider if any, reset record.info.layerIds = [].
4. Drop the stale engine table (record.tableName) and clear it, so the next ingest re-fetches fresh data.
5. Re-run the load pipeline preserving the current render mode/ingest mode/sourceLayer: _addGeoJSON(record, opts) for GeoJSON render, else _addViaEngine(record, opts). Passing the resolved renderMode ('geojson'/'tiles') keeps the chosen mode stable.
6. Emit layerupdated (same event setRenderMode uses) and return the refreshed info.
VectorControl.reloadLayer(id) delegates to this._layerManager?.reloadLayer(id). Expose it on the type declarations (dist/types) and the React imperative handle (VectorControlReact / react.ts) for parity with removeLayer/getLayers.
Add a unit test (vitest) that reloads a URL layer and asserts the id/sourceId are preserved and the data re-fetches. Release a new version (the opengeos packages publish to npm on gh release create).
GeoLibre wiring (after the upstream release)¶
- Bump the
maplibre-gl-vectordependency to the release that includesreloadLayer(). packages/plugins/src/plugins/vector-layer-sync.ts: addreloadLayerto theVectorSyncableControltype.packages/plugins/src/plugins/maplibre-vector.ts: export an accessor for the active control (or arefreshVectorLayer(id)helper) so the refresh handler can reach it.apps/geolibre-desktop/src/lib/layer-refresh.ts: treat a vector-control layer (type === "geojson",externalNativeLayer === true,sourceKind === "maplibre-gl-vector") with an HTTP(S) URL as refreshable, instead of bailing on theexternalNativeLayerflag.apps/geolibre-desktop/src/components/panels/LayerPanel.tsx: branchhandleRefreshLayerso vector-control layers refresh throughcontrol.reloadLayer(layer.id), while existing store-geojson layers keep therefreshGeoJsonLayer+updateLayerpath. The id is stable across reload, so the existing auto-refresh scheduler (keyed bylayer.id) keeps working without timer churn. Update the menu hint text accordingly.
Testing¶
- Upstream: vitest for
reloadLayer(id/source preserved, data re-fetched). - GeoLibre: frontend test that
isRefreshableLayerreturns true for a vector-control URL layer; manual verification in the running app that manual Refresh and Auto Refresh both work for an Add-Vector-Layer URL layer and the layer id/selection/position survive a refresh.
Sequencing¶
- GeoLibre PR: Part 1 + Part 2.
- maplibre-gl-vector: implement + test + release
reloadLayer(). - GeoLibre PR: bump dep + Part 3 wiring.