Skip to content

Commit ccaf754

Browse files
authored
Follow-up: notebook style review fixes (#1620)
* Address follow-up review feedback on #1619 Second round of bot review comments on #1619, which merged before they could be addressed on that branch. - docs/notebook.md: drop the `layer_id`-taking calls from the snippet instead of annotating them. The fire-and-forget client can never produce an id, so the example was self-contradictory; the calls are now described in prose below it, pointing at the widget client that does return ids. - console_api.py: give `load_geojson` the same `**style` kwargs as `add_geojson`, so both console entry points style in one call. - tests/scripting-style-params.test.ts: set the history coalesce window to 0 so the empty-style guard is actually under test (at the app's default window the two writes merge and the assertion passed either way — verified by mutating the guard), and add a case pinning the app-window behavior: a styled add coalesces into one undo entry, so one Ctrl+Z removes the layer rather than leaving an unstyled one behind. * Address review feedback - Capture the app's history coalesce window with `getHistoryCoalesceMs()` and restore that, instead of hard-coding 400 in the cleanup path and in the coalescing case, so the test follows the source default if it moves. * Address Claude review feedback - Document the `**style` kwargs on `load_geojson` in the Python console API table; the signature gained them in 6260a02 but the row still read `load_geojson(url, name=)`, inconsistent with the `add_geojson` row above it.
1 parent 40a199d commit ccaf754

4 files changed

Lines changed: 58 additions & 21 deletions

File tree

apps/geolibre-desktop/src/lib/pyodide/console_api.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -197,8 +197,11 @@ def add_geojson(self, data, name="GeoJSON", **style):
197197
fc = _coerce_featurecollection(data)
198198
return _js.addGeoJsonLayer(_to_js({"name": name, "geojson": fc, "style": style}))
199199

200-
async def load_geojson(self, url, name="GeoJSON"):
201-
"""Fetch a GeoJSON URL and add it as a layer (async). Returns the id."""
200+
async def load_geojson(self, url, name="GeoJSON", **style):
201+
"""Fetch a GeoJSON URL and add it as a layer (async). Returns the id.
202+
203+
Takes the same style overrides as :meth:`add_geojson`.
204+
"""
202205
from pyodide.http import pyfetch
203206

204207
response = await pyfetch(url)
@@ -207,7 +210,7 @@ async def load_geojson(self, url, name="GeoJSON"):
207210
if not response.ok:
208211
raise RuntimeError(f"Failed to fetch {url!r}: HTTP {response.status}")
209212
fc = _coerce_featurecollection(await response.json())
210-
return _js.addGeoJsonLayer(_to_js({"name": name, "geojson": fc}))
213+
return _js.addGeoJsonLayer(_to_js({"name": name, "geojson": fc, "style": style}))
211214

212215
def remove_layer(self, layer_id):
213216
"""Remove a layer by id."""

docs/notebook.md

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -36,11 +36,6 @@ m.add_geojson(
3636
) # GeoDataFrame, dict, or JSON string
3737
m.fit_bounds([-123, 37, -122, 38])
3838
m.set_basemap("https://…/style.json")
39-
40-
# Layer-targeted calls take a layer id. This client is fire-and-forget, so
41-
# `add_geojson` does not hand one back — see the note below.
42-
m.set_visibility(layer_id, False)
43-
m.remove_layer(layer_id)
4439
```
4540

4641
Calls are **fire-and-forget**: each posts a command to the host app over the
@@ -51,9 +46,13 @@ client source: `backend/geolibre_server/notebook_client.py`.
5146

5247
> Read-back queries (e.g. `get_center`) are not exposed by this fire-and-forget
5348
> client; they need the blocking request/reply path the `geolibre` widget uses.
54-
> Layer ids come from the same path: this client's `add_geojson` returns
55-
> `None`, while the widget's ([`geolibre` package](python.md)) returns the new
56-
> layer's id — which is what the id-taking calls above expect.
49+
50+
The client also has layer-targeted calls — `set_visibility(layer_id, visible)`,
51+
`set_opacity`, `set_style`, `remove_layer`, `zoom_to_layer` — but they are left
52+
out of the snippet above because there is no way to obtain a `layer_id` here:
53+
being fire-and-forget, this client's `add_geojson` returns `None`. Use them with
54+
an id from the blocking [`geolibre` widget](python.md), whose `add_geojson`
55+
returns the new layer's id.
5756

5857
## Driving the map from an external client (VS Code, …)
5958

docs/user-guide/python-console.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ is available this way.
124124
| `set_basemap(url)` | Set the basemap style (an http(s) or root-relative URL). |
125125
| `identify(lng, lat, layer_id=None)` | Query rendered features at a point (like a click). |
126126
| `add_geojson(data, name=, **style)` | Add a layer from a GeoJSON dict / geometry / `__geo_interface__`, with optional inline style overrides; returns the layer id. |
127-
| `await load_geojson(url, name=)` | Fetch a GeoJSON URL and add it; returns the layer id. |
127+
| `await load_geojson(url, name=, **style)` | Fetch a GeoJSON URL and add it, with the same optional inline style overrides; returns the layer id. |
128128
| `layers` | List of [`Layer`](#layer) objects, in draw order. |
129129
| `get_layer(layer_id)` | The `Layer` with that id (raises if absent). |
130130
| `remove_layer(layer_id)` | Remove a layer by id. |

tests/scripting-style-params.test.ts

Lines changed: 44 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import assert from "node:assert/strict";
2-
import { beforeEach, describe, it } from "node:test";
2+
import { afterEach, beforeEach, describe, it } from "node:test";
33
import { useAppStore } from "@geolibre/core";
4+
import { getHistoryCoalesceMs, setHistoryCoalesceMs } from "../packages/core/src/history";
45
import { styleParamPatch } from "../apps/geolibre-desktop/src/lib/scripting/style-params";
56

67
// The notebook client's `add_geojson(gdf, **style)` always sends a `style`
@@ -39,27 +40,61 @@ describe("styleParamPatch", () => {
3940
});
4041
});
4142

43+
// Captured before any test changes it, so the app's real window is what the
44+
// coalescing case exercises and what cleanup restores — not a copy of it that
45+
// would go stale if the default moved.
46+
const APP_COALESCE_MS = getHistoryCoalesceMs();
47+
48+
/** What the `addGeoJsonLayer` handler does with a raw `style` param. */
49+
function addGeoJsonCommand(name: string, style: unknown): string {
50+
const layerId = useAppStore.getState().addGeoJsonLayer(name, FC);
51+
const patch = styleParamPatch(style);
52+
if (patch) useAppStore.getState().setLayerStyle(layerId, patch);
53+
return layerId;
54+
}
55+
4256
describe("addGeoJsonLayer command styling", () => {
4357
beforeEach(() => {
58+
// 0 so each store write is its own history entry: with the app's default
59+
// coalesce window the two writes of a styled add would merge and the guard
60+
// below would pass whether or not it works.
61+
setHistoryCoalesceMs(0);
4462
useAppStore.getState().newProject({ name: "Scripting" });
4563
useAppStore.temporal.getState().clear();
4664
});
4765

66+
afterEach(() => {
67+
setHistoryCoalesceMs(APP_COALESCE_MS);
68+
});
69+
4870
it("merges an inline style into the new layer", () => {
49-
const layerId = useAppStore.getState().addGeoJsonLayer("Styled", FC);
50-
const style = styleParamPatch({ fillColor: "#facc15", strokeColor: "#d97706" });
51-
assert.ok(style);
52-
useAppStore.getState().setLayerStyle(layerId, style);
71+
const layerId = addGeoJsonCommand("Styled", {
72+
fillColor: "#facc15",
73+
strokeColor: "#d97706",
74+
});
5375

5476
const layer = useAppStore.getState().layers.find((item) => item.id === layerId);
5577
assert.equal(layer?.style?.fillColor, "#facc15");
5678
assert.equal(layer?.style?.strokeColor, "#d97706");
5779
});
5880

59-
it("costs a single undo step when no style kwargs were passed", () => {
60-
const layerId = useAppStore.getState().addGeoJsonLayer("Plain", FC);
61-
const style = styleParamPatch({});
62-
if (style) useAppStore.getState().setLayerStyle(layerId, style);
81+
it("writes nothing extra when no style kwargs were passed", () => {
82+
const layerId = addGeoJsonCommand("Plain", {});
83+
84+
assert.equal(useAppStore.temporal.getState().pastStates.length, 1);
85+
useAppStore.temporal.getState().undo();
86+
assert.equal(
87+
useAppStore.getState().layers.find((item) => item.id === layerId),
88+
undefined,
89+
);
90+
});
91+
92+
it("coalesces a styled add into one undo step at the app's window", () => {
93+
// The two writes land in the same tick, so the leading debounce in the
94+
// store's `handleSet` records only the first: one Ctrl+Z removes the
95+
// styled layer outright rather than leaving an unstyled one behind.
96+
setHistoryCoalesceMs(APP_COALESCE_MS);
97+
const layerId = addGeoJsonCommand("Styled", { fillColor: "#facc15" });
6398

6499
assert.equal(useAppStore.temporal.getState().pastStates.length, 1);
65100
useAppStore.temporal.getState().undo();

0 commit comments

Comments
 (0)