| 文件 | 最后提交记录 | 最后更新时间 |
|---|---|---|
feat(server-api): per-org OIDC federation and trusted-header proxy sign-in (2/3) (#2796) * feat(server-api): per-org OIDC federation and trusted-header proxy sign-in Refs #1682 * fix(server-api): harden proxy sign-in opt-in and OIDC HTTP client - Trusted-header sign-in now needs GEOLIBRE_PROXY_AUTH; GEOLIBRE_TRUSTED_PROXIES keeps its X-Forwarded-For meaning only. - GEOLIBRE_OIDC_CA_BUNDLE adds to the default trust instead of replacing it. - fetch_json enforces a 10-second total deadline across slow-drip bodies. - A federated account that cannot be created rejects the sign-in (400) instead of raising a 500. - Close pooled identity-provider connections on shutdown. * fix(server-api): block identity-provider requests to internal addresses Any user can create an organization and choose its identity provider's URLs. IdP connections now go through a network backend that resolves the host once, drops loopback, private, link-local, CGNAT, multicast, reserved, and unspecified addresses (including mapped, NAT64, 6to4, and Teredo forms), and connects to a checked address; TLS still verifies the URL's hostname. GEOLIBRE_OIDC_ALLOWED_NETWORKS admits internal networks; the CI interop and e2e SSO jobs allow loopback. * fix(server-api): keep SSO role changes consistent with sharing policy and break-glass - An SSO sign-in that lowers a member's role re-applies the public sharing policy, like PUT /members: public projects they can no longer publish become organization-only. The helper moves to projects.py with the project ORM models split out of main.py into project_models.py so oidc can use it without importing main. - The organization's break-glass account cannot be demoted, removed, or leave (422) until it is cleared on the provider, and SSO mapping never demotes it. * fix(server-api): fail closed if httpx drops the transport pool the egress guard replaces * test(e2e): move the enterprise SSO Playwright config beside its spec Move playwright.enterprise-sso.config.ts to e2e/enterprise-sso/playwright.config.ts so the repo root keeps a single Playwright config. Anchor the webServer cwd, outputDir and HTML report at the repo root so the build runs there and CI's artifact upload paths are unchanged. * Address Claude review feedback - oidc: verify azp whenever the ID token carries one, not only when aud lists several audiences (OIDC Core 3.1.3.7). - auth: reject decision=sso on a trusted-proxy request so proxy sign-in and organization SSO stay mutually exclusive, as the form and docs say. - enterprise_admin: turn the IntegrityError from two concurrent first PUTs of an organization's identity provider into a 409, not a 500. - egress: unwrap NAT64 addresses on the allowlist side too, matching the deny side, so an allowed IPv4 network admits its NAT64 form. - docs: note that requireMfa needs the IdP to emit the generic "mfa" amr value, or a mapper that adds it. --------- Co-authored-by: Qiusheng Wu <giswqs@gmail.com> | 2 天前 | |
test(e2e): pin identify-restore camera suppression in the core suite (#2500) (#2538) * test(e2e): pin identify-restore camera suppression in the core suite (#2500) Dismissing a resolved Identify popup restores the pre-Identify selection (#2498) and must not let "Zoom to selection" fit the camera to what it restored. That exemption rides on a read-once marker written by restoreIdentifySelection and read a commit later by MapCanvas's selection effect. tests/map-identify-lifecycle.test.ts pins the marker's exactly-once semantics, but nothing mounted a map to show the effect observing it in that window, so the guarantee rested on a manual camera-jump check. Adds e2e/identify-restore.spec.ts to the core project, driving the built web app: - Select a feature on one layer, identify a feature on another, dismiss with the popup's close button: the original layer and feature come back and the camera stays on the identified feature. - Change the selection while the popup is open: dismissal leaves that pick alone, camera included. - Click past the features: the Identify result clears, the identified layer stays selected, nothing is restored. This is the documented MapLibre/Mapbox parity behavior from #2498, recorded here so the difference from the close button is a choice the suite states. Supporting changes: two GeoJSON fixtures (one wide polygon, two small squares inside it, so a stray re-fit is a large camera move rather than a rounding difference) and a bindMapLibreMap helper that reaches the live map through the React fiber, the MapLibre counterpart of the bindMapboxMap the Mapbox specs already use. Verified against the negative case: stubbing consumePendingIdentifyRestore to always return false fails the first test with zoom 3.31 where 6.72 was expected, so the spec guards the real regression. * Address Claude review feedback - Name the miss-click's 12 px canvas inset as MISS_CLICK_INSET and say why that value is safe: it sits inside the 40 px FIT_BOUNDS_PADDING a fit reserves, so the just-fitted square cannot reach it. It read as an unexplained magic number relative to the padding it depends on. * Address Claude and CodeRabbit review feedback - Count camera movements instead of sampling positions across a dismissal. waitForCameraIdle's five still polls could in principle resolve before a delayed fit even started on a loaded runner, so the position comparison had a residual window in which a re-fit could pass unseen. A movestart counter, reset just before the dismissal, answers "did the camera move at all since then" and closes that window. It also reports the regression more sharply: with the suppression stubbed out the failure is now "Expected 0, Received 2" rather than a zoom delta. - Anchor the miss-click to the identified square's own projected west edge rather than to an inset calibrated against FIT_BOUNDS_PADDING, which is module-private in map-controller.ts and linked only by comment. The gap now holds whatever that constant becomes, and an explicit assertion on the projected edge says so if a future fit leaves no room west of it. - Check every method of TestMapHandle before accepting a candidate in bindMapLibreMap. The fiber walk met many hook values and probed only three of the methods the specs go on to call, so a partial value could have been stashed and thrown later from an unrelated line. * Address Claude review feedback on the miss-click anchor - Take the miss-click's y from the projected west-edge point too, not from the canvas's vertical centre, so both coordinates are anchored to the feature rather than one to the feature and one to the viewport. The click was already a guaranteed miss (north-up camera, so the square projects axis-aligned and anything west of its west edge is outside it at any latitude), but the mixed anchors left that needing an argument rather than being evident; the comment now states the north-up premise. --------- Co-authored-by: giswqs <5016453+giswqs@users.noreply.github.com> | 12 天前 | |
ci(preview): smoke-test each deployed PR preview for a clean console (#2922) * ci(preview): smoke-test each deployed PR preview for a clean console Previews deploy to Cloudflare under /demo/, but nothing loaded the deployed app, and no spec failed on console errors. That is how #2916 (deployment.json 404) and #2919 (base-path fetches from the site root) reached web.geolibre.app and every preview unnoticed. - e2e/preview/: a config and spec that load PREVIEW_URL, wait for the MapLibre map to report loaded(), and fail on any console.error, page error, or same-origin 4xx/5xx. - pr-preview-deploy.yml: a smoke job runs it against the Cloudflare deployment from a default-branch checkout with a read-only token, and a smoke-status job posts the result as a commit status on the PR head, since workflow_run jobs do not appear in a PR's checks. - smoke.spec.ts (core): the same clean-console check against the local build on every PR (~3 s). - collectPageProblems and waitForMapLoaded shared in e2e/helpers.ts. * Address review feedback - collectPageProblems: always count the page's own document response. It can arrive before the frame commits its URL, so the same-origin check against page.url() dropped a 404'd app (Claude review). - preview config: collapse repeated slashes in PREVIEW_URL, so a deployment URL with a trailing slash plus "/demo/" still loads the app's directory (Claude review, CodeRabbit). - preview spec: attach and log the collected problems even when an earlier assertion fails first, so a 404'd app reports its status. - pr-preview-deploy.yml: document why smoke-status needs statuses: write (CodeRabbit / zizmor). | 52 分钟前 | |
test(e2e): move four feature specs and registry deep-link flows to nightly (#2918) * test(e2e): move four feature specs and the registry deep-link flows to nightly The core Playwright gate grew to ~11 min wall-clock (10.6-15.8 min over the last three main runs, ~25 min of test time), with identify-restore retrying on most runs. Move set-view, paste-style, style-manager and identify-restore to the nightly features project: each covers one dialog or interaction rather than something every user hits. Split the two registry install tests out of plugin-deep-link.spec.ts into plugin-deep-link-registry.spec.ts (nightly), so the docs-vs-link- name check that #2858 put in core stays per-commit. Core drops from 44 to 30 tests; the full suite still runs all 150. * test(e2e): accept either Whitebox search label in the a11y sweep The toolbox search box reads "Search tools or describe a task" only when an AI endpoint is configured (useWhiteboxSemanticSearch). CI builds have none, so the Processing toolbox sweep failed there while passing on machines with a key. Match the shared "Search tools" prefix. * test(e2e): open Plugins -> Installed in plugin-install.spec #2851 moved external plugins into an Installed submenu, so every plugin-install case that looked for the plugin at the top of the Plugins menu failed (the nightly suite has been red since). Add an openPluginsMenu helper that opens the submenu, waiting for it where a plugin is expected because external plugins load asynchronously. Also wait for the unsaved-changes prompt or the New project dialog instead of probing once for the prompt: it opens a beat after the menu closes, so the probe missed it and the test waited behind it. | 1 小时前 | |
fix(a11y): un-nest layer rows and broaden the axe sweep (#2895) * fix(a11y): un-nest layer rows and broaden the axe sweep The layer panel rendered each row as a role="button" card wrapping its own controls, a serious axe nested-interactive finding the a11y specs allowlisted. Rows, group headers and the Background card are now listitems in a labelled list, carrying their depth as aria-level, and selection moves to a dedicated name button with aria-pressed. Both allowlists (a11y.spec.ts, rtl.spec.ts) are gone. A new nightly a11y-screens.spec.ts scans the Add Data panels and dialogs, the Processing toolbox, the Style panel for a vector and a raster layer, every Settings section, New Project with Examples, Project History, Print Layout, Manage Plugins, and an error toast. It found and this fixes: unnamed selects in Geocoding settings and the Whitebox toolbox, and the raster panel's faint text at 3.5:1. keyboard-only.spec.ts (core) adds an OGC API - Features layer by URL, changes its circle radius, and exports it as GeoJSON with no pointer input. The shared axe helper waits for animations so a mid-fade frame is not reported as a contrast failure. Part of #2858 * Address review feedback - LayerRow: F2 on a layer's name button starts a rename, the keyboard counterpart of the double-click (CodeRabbit), with a component test. - a11y-screens.spec.ts: slugify the Add Data attachment labels. - a11y.ts: bound the animation wait at 10 s so a stuck animation fails with a clear error. - keyboard-only.spec.ts: tabTo treats a not-yet-rendered target as not focused instead of stalling on the default action timeout. - layer-panel.spec.ts: update a comment that still described the card as a role="button" wrapper (Claude review). * Address Claude review feedback - Drop the data-layer-card markers on the layer and Background cards: their only consumer was the nested-interactive allowlist this PR removes. | 6 小时前 | |
fix(a11y): un-nest layer rows and broaden the axe sweep (#2895) * fix(a11y): un-nest layer rows and broaden the axe sweep The layer panel rendered each row as a role="button" card wrapping its own controls, a serious axe nested-interactive finding the a11y specs allowlisted. Rows, group headers and the Background card are now listitems in a labelled list, carrying their depth as aria-level, and selection moves to a dedicated name button with aria-pressed. Both allowlists (a11y.spec.ts, rtl.spec.ts) are gone. A new nightly a11y-screens.spec.ts scans the Add Data panels and dialogs, the Processing toolbox, the Style panel for a vector and a raster layer, every Settings section, New Project with Examples, Project History, Print Layout, Manage Plugins, and an error toast. It found and this fixes: unnamed selects in Geocoding settings and the Whitebox toolbox, and the raster panel's faint text at 3.5:1. keyboard-only.spec.ts (core) adds an OGC API - Features layer by URL, changes its circle radius, and exports it as GeoJSON with no pointer input. The shared axe helper waits for animations so a mid-fade frame is not reported as a contrast failure. Part of #2858 * Address review feedback - LayerRow: F2 on a layer's name button starts a rename, the keyboard counterpart of the double-click (CodeRabbit), with a component test. - a11y-screens.spec.ts: slugify the Add Data attachment labels. - a11y.ts: bound the animation wait at 10 s so a stuck animation fails with a clear error. - keyboard-only.spec.ts: tabTo treats a not-yet-rendered target as not focused instead of stalling on the default action timeout. - layer-panel.spec.ts: update a comment that still described the card as a role="button" wrapper (Claude review). * Address Claude review feedback - Drop the data-layer-card markers on the layer and Background cards: their only consumer was the nested-interactive allowlist this PR removes. | 6 小时前 | |
fix: preserve ArcGIS projects when the CDN is unavailable (#2459) * Add ArcGIS basemap picker and native layer control * Support native ArcGIS heatmaps, clusters, patterns and feature altitude * Render custom COG terrain in ArcGIS scenes * Render COG imagery on ArcGIS maps and scenes * Host deck.gl visualizations on ArcGIS maps and local scenes * feat: render PMTiles and MBTiles archives with ArcGIS * feat: render Zarr and NetCDF grids with ArcGIS * feat: adapt data plugin controls to the ArcGIS renderer * fix: preserve ArcGIS projects when the CDN is unavailable * Address review feedback - Reuse the existing ArcGIS translation key for the basemap section. - Read the basemap preferences action through component selectors. - Synchronize native visibility callbacks to prevent store updates losing toggles; add a regression test. - Clarify the shared primary-pane Layer Control default; keep the widget consistent with the active-by-default plugin. * Address review feedback - Read current preferences when selecting an ArcGIS basemap to preserve concurrent changes. - Document all ten supported Esri basemap choices. - Allow the two-boot Style Manager E2E flow sufficient total runtime on CI without changing assertion timeouts. * Address review feedback - Skip unused symbol evaluation and deduplication for heatmap points, preserving mixed geometry styling. - Explain the inclusive cluster zoom cutoff already covered by the crossover test. * Address review feedback - Restore custom terrain only for 3D scenes and isolate DEM failures from view initialization. - Keep a redacted diagnostic for failed restores while preserving the working map. * Address review feedback - Strip session-only raster byte URLs from saved and reopened projects while preserving desktop paths. - Initialize COG extents synchronously in Web Mercator from known bounds. - Fall back to COG when a source URL has no trailing name. * Address review feedback - Remove an unused vendored import and align ArcGIS scale constants and binding documentation. - Honor ArcGIS diagram zoom limits and decluttering using the native projection and settled camera updates. - Omit unprojectable diagram points and invalidate native-view decluttering on camera changes. * Address review feedback - Translate PMTiles validation errors in all shipped locales, including malformed URLs. - Share native archive layer construction between dialog and plugin imports. - Document per-source-layer native vector tile decoding costs. * Address review feedback - Translate Zarr URL and color-limit validation in every shipped locale. - Exclude configured spatial axes from Time Slider dimensions. - Refresh time slices in place, preserving the native layer, coordinate metadata and byte cache while cancelling stale renders. * Address review feedback - Explicitly ground selection highlights when feature elevation is disabled. - Preserve finite zero-Z coordinates for ArcGIS absolute-height placement and altitude offsets. - Add regression coverage for both altitude cases without changing default Z detection in other renderers. * Address review feedback - Clear a failed remembered terrain source to avoid retrying it on every view rebuild. - Refresh cache recency for reused terrain neighbours during panning. * Address review feedback - Reuse opened COG sources across symbology rebuilds and forget removed sources. - Cache source statistics weakly and verify style edits use the new gamma without reopening data. * Address review feedback - Clear failed ArcGIS overlay mount promises so a transient CDN failure can be retried. - Reuse the SDK module URL helper and verify retry succeeds on the same view. * Address review feedback - Accept query parameters on archive source metadata requests, matching tile request handling. - Verify source metadata is identical with and without SDK query parameters. * Address review feedback - Compare immutable kerchunk manifests by identity instead of serializing large reference maps during view updates. - Register native NetCDF time axes once with their CF metadata and persist references before adding the layer. - Verify reference-only metadata resolution and rendering signature behavior. * Address review feedback - Route 3D Tiles panel and initial-load camera actions through the active native renderer. - Coalesce ArcGIS hover picking per animation frame and clean up queued work and failed mount listeners. - Document the host-owned single picker and adapter registration contract. * Address review feedback - Preserve the project when WebKit omits the failed module URL, while retaining recovery for identified local chunks. - Derive the production CSP browser-test route from Playwright baseURL. - Verify the installed reload handler and real CDN, CSP and offline browser paths. * Address review feedback - Restore terrain only when enabled without delaying view startup, retaining the selection after transient failures. - Exercise overlapping terrain requests, failed replacements and disposal. - Distinguish elevation axes and return zero outside world Y bounds. * Address review feedback - Offer the ArcGIS PMTiles dialog from Add data to group. - Verify native archive interceptors are replaced on restyle and released on removal or SDK failure. * Address review feedback - Clamp both polar Zarr extent endpoints to the Web Mercator range without inverting bounds. - Verify north and south polar datasets retain ordered, valid extents. * Address review feedback - Translate raster-source validation across all shipped locales. - Clarify the missing-control test now that ArcGIS hosts plugin controls. - Cover releasing a Mapbox projection lock after switching to ArcGIS. * Address review feedback - Use the ArcGIS unsupported-source tooltip for disabled ArcGIS imports. - Describe deck overlay availability across all supported renderers. * Address review feedback - Type the terrain registration fixture explicitly so tile URLs retain their tuple shape. * Address review feedback - Retry COG preparation after transient failures instead of retaining a rejected promise. - Skip raster lifetime scans when the layer list has not changed. * Address review feedback - Use the translated PMTiles layer name when the editable name is blank. - Reuse the shared layer ID helper for archive imports. * Address review feedback - Allow native Zarr layers to retry initialization after transient store failures. - Verify recovery through the real Zarr metadata reader on the same native layer. * Address review feedback - Cover local raster restoration, blob lifetime, skipped sources and stale asynchronous reads. - Document browser blob reuse in the raster restore path. * Address review feedback - Clarify that deck layer ordering is internal to the shared overlay. - Include the lazy deck SDK modules in the ArcGIS upgrade checklist. - Explain projection-driven capabilities during ArcGIS view replacement. * Address review feedback - Unregister temporal adapters when native ArcGIS Zarr layers leave the store. - Verify removal before any MapLibre control installs its cleanup listener. * Address review feedback - Make plugin jumpTo calls use the engine's nonanimated camera path. - Exercise DOM control mounting, movement events, initialization failure and teardown. * Address review feedback - Store untrusted kerchunk keys without inheriting object prototype setters. - Validate CF scale and offset before rendering Zarr tiles. - Document the native ArcGIS Zarr query API limitation. * Address review feedback - Clear failed PMTiles header promises so later raster tile requests can retry. - Verify header recovery on the same native raster archive layer. * Address review feedback - Pass the host map facade to adapted controls for their teardown hook. - Call deck control onRemove once before releasing its native overlay. - Verify plugin cleanup and a subsequent mount lifetime for both deck overlay types. * Address review feedback - Preserve custom raster names when selecting a file and use the shared filename helpers. - Translate ArcGIS 3D Tiles source validation through the host in all shipped locales. * Address review feedback - Clarify unsupported layer labels for the current ArcGIS view across all locales * Address review feedback - Measure scene camera distance relative to the focal elevation - Retry 2D resource initialization with bounded attempts and disposal guards * Address review feedback - Release every partial Deck and GPU allocation before retrying initialization * Address review feedback - Release every partial Deck and GPU allocation before retrying initialization * Address review feedback - Validate programmatic PMTiles URLs and tolerate malformed display escapes - Derive missing MBTiles zoom metadata from the archive tile rows * Address review feedback - Preserve PMTiles deep-link URLs when opening the Add Data dialog - Query MBTiles tile zoom bounds only for missing metadata values * Address review feedback - Preserve PMTiles deep-link URLs when opening the Add Data dialog - Query MBTiles tile zoom bounds only for missing metadata values * Address review feedback - Keep inline local NetCDF refs out of persisted projects - Avoid redundant Zarr cleanup scans and refresh byte-cache recency - Cover coordinate-free bounds orientation and reject blank color limits * Address review feedback - Validate raster URLs and skip overlay listeners for unsupported scenes - Gate command and shortcut deck sources on ArcGIS view capability - Report non-cancellation 3D Tiles navigation failures * Address review feedback - Distinguish permanent ArcGIS incompatibility from current-view deck limits - Add the view-scoped tooltip across all translated catalogs * Address review feedback - Detect geographic proj4 definitions by their coordinate transform - Restyle native Zarr tiles without rebuilding grid metadata and caches - Preserve narrow antimeridian extents for zoom-to-layer * Address review feedback - Preserve adapted-control identify results across a concurrent view teardown * Address Zarr review feedback * Address Zarr review feedback * Resolve Zarr EPSG definitions * Resolve Zarr EPSG definitions * Resolve Zarr projections offline * Resolve Zarr projections offline * Fix projection dependency lock * Fix projection dependency lock * Keep compatible projection definitions * Keep compatible projection definitions * Track restored Zarr layers by id * Track restored Zarr layers by id * Protect authenticated Zarr requests * Protect authenticated Zarr requests * Reject authenticated Zarr redirects * Reject authenticated Zarr redirects * Expose Zarr in group add menu * Expose Zarr in group add menu * Address ArcGIS controls review * fix: address ArcGIS controls review * fix: forward empty ArcGIS deck clicks * fix: forward empty ArcGIS deck clicks * fix: clear previous ArcGIS deck hover * fix: align ArcGIS data and picking behavior * fix: harden ArcGIS CDN recovery | 16 天前 | |
feat(arcgis): scene zoom and bounds limits and a nautical scale bar (#2624) * feat(arcgis): scene zoom and bounds limits and a nautical scale bar Replace the SDK ScaleBar with the 2D map's body-aware scale control, which draws nautical miles and measures a scene at its centre. A SceneView now honours the project's zoom range (as an altitude range on a globe, and by easing back once the camera settles) and its restricted bounds. A flat view's zoom limits become scales taken from the view's own levels, so a fractional minimum (restricted bounds raise it until they fill the view, as on MapLibre) no longer breaks the SDK. The bounds helpers move to a module both engines share. * fix(arcgis): hold a flat view's zoom range and bounds without SDK limits With continuous zoom (snapToZoom off) the SDK refuses a wheel step that would cross a zoom limit instead of stopping at it, and a zoom limit with the lateral geometry stopped the wheel zooming out at all. The flat view now sets no SDK limits: the wheel stops at the project's zoom range, and a view that settles past the range or the bounds eases back, as a scene already did. The limits are reapplied once the view has its size (the bounds' minimum depends on it) or another basemap's levels, the settle check tolerates the SDK's projection round trip, and the scale bar follows rotation. * fix(arcgis): let the Controls menu toggle the scale bar in a scene The engine still refused the scale control in a SceneView, although the new scale bar measures a scene. The navigation-limit watch now returns a string, so camera frames that re-evaluate its getter do not re-run it, and the docs say the wheel guard is flat-map only. * refactor(arcgis): type the SDK mouse-wheel event's deltaY * fix(arcgis): set a scene's altitude range only once the view is sized * fix(arcgis): hold the settle correction until the stored camera is placed | 10 天前 | |
feat(geolens): edit and save vector data back to a GeoLens dataset (#1430) * feat(geolens): edit and save vector data back to a GeoLens dataset A GeoLens dataset added as GeoJSON is already an ordinary editable GeoLibre layer — the GeoEditor can redraw it in place and the attribute table can retype it — but nothing carried those edits back to the server. GeoLens does expose per-feature CRUD (`POST /api/datasets/{id}/features/`, and `PUT`/`PATCH`/ `DELETE` on `.../features/{gid}`), so the round trip only needed a client. The plugin now tracks a baseline for every dataset it loads, shows what changed in an Edits section, and writes the difference back: - Identity is the integer row id GeoLens already returns as the GeoJSON `id`, so it survives the store, the attribute table, and the GeoEditor's tag-and-restore round trip. Features it never held are inserts; baseline ids no current feature claims are deletes. - Attribute changes go out as PUT (full replacement) because GeoLens does not document whether PATCH merges the properties map, and a merge would silently keep an attribute the user cleared. A geometry-only move PATCHes. - There is no bulk/transaction endpoint, so a save is one request per changed feature, issued sequentially with progress. A rejected write is reported and the rest of the plan continues; the baseline then advances only for the writes that landed, so a partial save leaves exactly the failures pending rather than absorbing them. - Row ids assigned to inserted features are stamped back onto the layer, so a second save updates those rows instead of inserting duplicates. - Saving is offered only when the server's `enable_dataset_editing` flag is on (read from the public `/api/settings/feature-flags/`) and the connection carries an API key — the write endpoints reject anonymous requests. The panel says which of the two is missing instead of showing a dead button. - A restored project has no in-memory baseline, so the first save reads the dataset back from GeoLens and diffs against what it holds now. Reload discards local changes the same way. Verified in the browser against a live GeoLens (datasets.geolibre.app, editing flag off → Save correctly disabled, counts still track a real attribute-table edit) and against a local stand-in with editing enabled: a GeoEditor-drawn feature POSTs, an attribute edit PUTs, and the row settles back to no pending changes. * feat(geolens): add a Sample server dropdown to the panel Trying the plugin previously meant already knowing a GeoLens URL. The panel now offers the two public deployments — datasets.geolibre.app and demo.getgeolens.com — above the URL field. Picking one fills the field and connects (that is the whole intent of the choice; leaving Connect to a second click would only add a step), then resets to the placeholder, because the URL field stays the source of truth and the user can edit it afterwards. Also makes a blocked request legible. demo.getgeolens.com serves its catalog to curl but sends no `Access-Control-Allow-Origin` at all, so no browser can reach it — where datasets.geolibre.app allowlists the requesting origin and works. `fetch` reports that as a bare TypeError ("Failed to fetch") with no detail by design, which read as "GeoLibre is broken" rather than "this server does not allow browser access". Every failure this module raises itself is a plain Error, so the constructor cleanly separates the two, and a transport failure now names the host and says the server refused a cross-origin request. Verified in the browser, light and dark: the dropdown lists both entries, the GeoLibre catalog loads (22 datasets), and the demo entry shows the CORS message instead of a bare fetch error. * fix(geolens): clear the catalog when switching or failing to reach a server Connecting to a different server left the previous one's dataset cards on screen, so a failed connect showed an error above what looked like that server's catalog. Those cards are worse than stale: their Add buttons build source paths from the old base URL, and the panel has already dropped the client, so they describe a server it is no longer pointed at. Connect now empties the list before querying the new server, so switching clears immediately rather than after the response arrives, and any failed request empties it too — after a request that never landed the panel does not know what the server holds, and leaving earlier results up presents them as the answer to a query that never ran. Verified in the browser: connect to datasets.geolibre.app (22 datasets), switch to the demo server that no browser can reach (0 datasets, just the CORS message), switch back (22 datasets). * fix(geo-editor): keep a layer's attributes through an in-place geometry edit Geoman claims eleven property names as its own "shape properties" — id, shape, center, width, height, xSemiAxis, ySemiAxis, angle, text, disableEdit, group. On import it reads them from a feature's plain attributes, and on export `parseExtraProperties` deletes both the plain and the prefixed form and re-emits the value as `__gm_<name>`. So a layer with a `height` or `id` column — building footprints, anything carrying a source id — came back from a pure geometry edit with those columns *renamed* to `__gm_height` / `__gm_id`, plus a `__gm_shape` column that was never in the data. The attribute table showed the renamed columns; the original ones were gone from the layer. The session only ever edits geometry, so the attributes it started with are the attributes it must end with. `startLayerGeometryEdit` now snapshots them (from the already-tagged collection, before Geoman sees it) and the write-back restores them by feature tag, dropping Geoman's `__gm_*` bookkeeping. A feature drawn during the session has no snapshot, so it keeps its own properties minus that bookkeeping, which would otherwise appear as columns in the layer. Found via the GeoLens plugin, where it also made every feature look edited: an edit session on the 540-feature Las Vegas Buildings demo dataset marked all 540 changed and would have PUT `{}` over every row's attributes — the geometry was byte-identical, only the renamed columns differed. Also stops GeoLens counting a dropped null-valued attribute as a change: a GeoLens row exposes every column, so an empty feature loads as `{"id": null, "height": null}` and returns from the editor as `{}`. Absent and null both leave the column NULL, so folding them together drops writes that could not change anything, while a real value that disappears still registers. Verified against the live dataset: a session with no edits went from 540 writes to none ("No local changes", Save disabled) with the attribute table keeping its id/height columns, and a single-feature attribute edit now issues exactly one PUT (HTTP 200, "Saved 1 change to GeoLens"). The demo row used for that check was restored to its original values. * feat(attribute-table): show feature, filtered and selected counts in a status bar The table had no readout of how much data it was showing: a layer's size was only visible in the Layers panel, and the size of a selection nowhere at all. A status bar under the table now reports the layer's feature count and how many features are selected. The count of rows currently shown appears only when a search or the Selected view is narrowing the table — showing it always would print the same number twice in the ordinary case and teach the eye to skip it. It sits outside the scroll area so it stays put while scrolling, and is hidden when the panel is collapsed or the layer has no attributes. Counts come from the same collections the table renders (`attributeRows`, `filtered`, `selectedFeatureIds`), so they cannot drift from what is on screen. Strings are plural-aware (`_one`/`_other`) in en.json; the other catalogs fall back to English until translated. Covered by a hermetic e2e spec over the existing smoke fixture: totals, the "shown" count appearing only while filtered, and selection. * Address CodeRabbit review feedback - saveLayerEdits: baseline the collection that was actually diffed and written, not a fresh read of the store. An edit made while the save was in flight was being snapshotted as if the server already held it, so it would read as unchanged forever and could never be saved. Covered by a test that fails against the previous logic. - Prune editSessions/pendingCountsCache for layers that have left the store. Each entry holds a full copy of its dataset, so adding and removing GeoLens layers retained one dataset per discarded layer until the plugin deactivated. - Reload now confirms before discarding unsaved changes, naming the counts it would throw away. A clean layer still reloads on a single click. - Clear the API key when the target server changes origin (sample-server pick or a retyped host), so a key issued by a private deployment is not carried to a different one. Manual edits check on `change`, not per keystroke, so fixing a path within one host leaves the key alone. - Reword the transport-failure message: a bare fetch TypeError also covers DNS, offline and TLS failures, so it no longer asserts the server refused CORS — it says the request never completed and names CORS as one possibility. - captureEditedProperties reads the pre-tag collection, so a feature whose properties were null stays null through tag → edit → reconcile instead of becoming {}. Regression test added for the null round trip. - Test the properties-only PATCH branch (mode "patch" with no geometry), both in updateFeature and in the diffFeatures fallback that selects it. * feat(geolens): load Add GeoJSON from the current map view Add GeoJSON took the first N features of a dataset, which on a large catalog layer is an arbitrary slice with no relationship to what the user is looking at. It now asks the server for the features inside the current map view (OGC `bbox`), so the feature limit caps what is loaded *from that area* instead. Verified against the Las Vegas Buildings demo dataset: zoomed into a few blocks, the load returned 49 of 534 features — the ones on screen. - Settings gains "Only load features in the current map view", persisted like the feature limit, and on by default: a catalog dataset is usually far larger than the area being looked at. A view that spans the world, wraps the antimeridian, or has no map sends no bbox at all rather than a wrong half. - A view-filtered layer is named "<dataset> (current view)", because the Layers panel is where someone will later wonder why the layer is a subset. - The load terms (limit and extent) are recorded on the layer, and the baseline and Reload now re-read on those terms rather than the panel's current settings. Without this a view-filtered layer would diff against the whole dataset and the next save would DELETE every feature outside the view. The same recording fixes the pre-existing case of changing the limit between load and save. - A save that would delete features now asks first, naming the count. Deletions are the only part of a plan that can appear without the user touching a feature — one the editor silently failed to load is simply absent afterwards, which diffs identically to a deliberate delete — and the one part that cannot be undone. * fix(map): re-render vector tiles when their endpoint changes Adding a dataset as vector tiles, editing it as GeoJSON and saving left the tiles showing pre-edit data at every zoom the user had already looked at, while zooms visited for the first time came back correct — removing and re-adding the layer fixed it. Three things were wrong, in order of depth: - `syncVectorTileLayer` created its source once and never touched it again, so a changed tile template never reached MapLibre. It now pushes the new endpoint into the live source with setTiles/setUrl, and only when it actually changed (a needless reload blanks the layer for a frame). This also repairs the GeoLens token refresh, which patched the store every few minutes and, as it turns out, never affected the map at all — a layer left open past its token's lifetime would have started 404ing. - Saving edits did not tell the tiles anything had changed. A successful save now re-points every GeoLens vector-tile layer showing that dataset, which drops MapLibre's cached tiles and re-requests them. - Re-minting a token is not enough to do that: GeoLens returns the *same* signature and expiry for the rest of its time bucket, so the URL would be unchanged and both MapLibre and the HTTP cache would answer from cache. The refresh stamps a `_v` parameter instead. Signature validation ignores unknown parameters — verified against the live server: the same tile returns byte-identical content (5414 bytes, HTTP 200) with and without it. Verified end to end on the Las Vegas Buildings demo dataset: with the map at zoom 15 and those tiles already rendered, saving one edit re-requested them immediately, and every request after further navigation carried the new version. Also fixes the attribute-table type inference that this exposed. A cell holding null carried no type, so typing into one stored a string — which made a number column mixed, and a PostGIS-backed dataset rejected the write outright ("a value is incompatible with a column's type or constraints"). The type now comes from the column across the layer, falling back to the raw string only when the column is empty everywhere. * feat(geolens): re-scope a loaded layer to the current map view Moving to a different area meant removing the GeoJSON layer and adding it again. Each Edits row now has a "Load this view" button that replaces the layer's features with the ones in the current view, in place. Unsaved work is never discarded silently. With pending changes the button offers to save them first; a save that did not fully succeed aborts the reload, so the features those writes failed on are still there to retry. Declining the save asks separately about discarding, so save, discard and cancel are all reachable from the one button — the existing Reload keeps its narrower meaning of "discard and re-read the same extent". The refresh records the new extent and limit on the layer and re-baselines from what it loaded, so the features that just left the view are not mistaken for deletions on the next save. An auto-generated layer name is kept accurate (gaining or losing the "(current view)" suffix); a name the user changed is left alone, which is why the dataset title is now recorded in the layer metadata. | 2 个月前 | |
test(e2e): fix the blend-modes flake and widen the per-commit gate (#2876) * test(e2e): fix the blend-modes flake and widen the per-commit gate Triage of the nightly E2E full failures (09-21, 09-22, 09-27, 10-03): every one but blend-modes was a real break already fixed on main (#2523, #2548, #2703, #2832). blend-modes needed a retry on 6 of the last 17 nights: it sampled its unblended reference pixel while the landing zoom's basemap tiles were still streaming in after the drop's fly-to. Wait for the polygon under the centre and an idle map first. Widen what gates a PR so those breaks fail their own PR: - layer-groups (Project -> Save -> reopen) and plugin-deep-link move to `core`. - New `core-engines` project: one smoke pass each for Cesium and ArcGIS (switch renderer, add GeoJSON, identify). It runs after `core` on one worker; beside the core workers on four CPUs it starved them and itself. - E2E_PORT overrides the preview port. Part of #2858 * Address review feedback - waitForCameraToLand could poll forever when a flight lands on the zoom it started from. The PR's own CI run hit a worse version of this: the Cesium globe intermittently refuses both the drop's fly-to-layer and a Set View to the dropped layer's centre (#2878), so the zoom never moved. Replace it with waitForSettledZoomNear, which waits for a target zoom by value, and park the camera with Set View on MapLibre before the engine swap so the pick needs no flight. The popup content is checked inside the click retry, since a late fly-to-layer closes an open popup. Part of #2858 | 12 小时前 | |
fix: unbreak the nightly E2E full suite (Overture release discovery + Cesium token hint) (#2548) * fix(overture): resolve the release from the tile distribution Overture froze its releases.json at 2026-07-22.0, deprecated it, then removed that release's tiles: the distribution bucket now carries only 2026-08-19.0. Both Overture plugins read `latest` from that document, so the Overture Maps control drew nothing and Overture Query fetched 404s. Take maplibre-gl-overture-maps 0.4.1, which lists the distribution bucket instead and falls back to the STAC catalog, and move Overture Query onto its `resolveReleases`. The PMTiles control's "Overture buildings" sample was pinned to the same dead release; point it at one that exists. * test(e2e): stop pinning the Settings label inside the Cesium token hint The keyless assertion matched the whole hint sentence, including the label of the Settings button interpolated into it. #2526 reworded that label to "Settings → Environment variables" and the nightly E2E full run has failed on this spec ever since, with a message blaming a stray Ion token — the one thing that was not wrong. Match the leading phrase instead, which is what actually distinguishes the keyless path, and leave the button's wording to the catalog. | 12 天前 | |
test(e2e): seed the Cesium renderer-swap camera deterministically (#2333) `swaps the primary map, keeps the project, and swaps back` seeded its 2D camera with a burst of wheel events and then read the status bar back through `waitForStableZoom`. That readout is a step function: `MapCanvas` publishes `mapView` on MapLibre's `moveend` alone, so while an ease is still running the bar keeps reporting the *previous* settled camera and two identical samples 500 ms apart look stable. The recorded zoom is then one the map moves on from, the globe seeds from the later camera, and `expectSameZoom` fails by more than the conversion tolerance: Error: zoom 13.18 vs 14.46 Error: zoom 15.14 vs 14.57 Here the burst also lands while the map may still be flying to the layer just dropped, which is why this test flakes and its sibling no longer does: #2293 moved the toolbar test off the same wheel burst and onto the Set View dialog, whose requested endpoint can be waited for by value. Do the same here, and share that seeding as `setView` so both tests use one explanation. The seed is asserted for equality before the engines swap, so a camera that has moved on fails at the seed rather than surfacing later as an unexplained disagreement between the engines. | 26 天前 | |
feat: load deployment.json before first paint (#2783 part 2) (#2795) * feat: load deployment.json before first paint (#2783 part 2) * fix: address review on deployment policy loading (#2783 part 2) * perf: load App in parallel with deployment.json; trim catalog id/name (#2783 part 2) * fix: treat an empty deployment.json interface as absent (#2783 part 2) * fix: set the app-name title up front; document capabilities fail-open (#2783 part 2) | 2 天前 | |
fix(shell): recover a drop overlay that outlived its drag (#1683) * fix(shell): recover a drop overlay that outlived its drag The drag-and-drop overlay is one boolean fed from two independent places. The webview drag handlers balance it with a depth counter, but on desktop Tauri's native onDragDropEvent sets it with no counter at all, since the OS reports enter/leave/drop directly. Either feed can strand it: a native "leave" the OS never delivers (what a modal native file dialog opening mid-drag produces on WebKitGTK, which is how the reporter hit it via the style import dialog), or an unbalanced webview enter/leave pair that leaves the depth counter above zero. Both leave the overlay covering the map with nothing left to clear it, which is why neither Escape nor a resize helped and only dragging another file across the window dismissed it. Rather than chase every way the OS can swallow a drag event, recover on two signals that cannot occur while a real drag is in progress: a key press and a pointer button. HTML5 drags suppress mouse events for their duration and native drags hold a pointer grab, so neither can fire mid-drag and dismiss the overlay out from under a drag the user is actually performing. - Escape clears the overlay, which is what the reporter reached for first. - A pointer press clears it too, so it heals on the next interaction without the user knowing to press anything. - Both reset the depth counter, so the two feeds cannot disagree afterward. - Add a data-testid to the overlay and e2e cover for both recovery paths plus a guard that a balanced drag still shows and hides it. Fixes #1664 * Address review feedback - Listen for keydown and pointerdown in the capture phase rather than the bubble phase. Several controls stop propagation before these events reach window (startLayerPanelResize in this same file is one, and a focused Radix dialog handles its own Escape), which silently defeated the recovery for exactly the interaction a user is most likely to try first: clicking a panel control while the overlay is stuck. Covered by a new e2e test that presses the layer panel resize handle, which fails on the bubble phase. - Add an e2e test that a real GeoJSON drop still imports a layer and leaves the overlay hidden, so the recovery listeners cannot swallow the drop the overlay exists to invite. This was verified by hand before but not encoded. * Address review feedback - e2e/drop-overlay.spec.ts: drive the resize-handle test with a real `click()` instead of `dispatchEvent("pointerdown")`. A synthetic pointer event carries no active pointer id, so `startLayerPanelResize`'s `setPointerCapture` could throw before the capture-phase recovery was exercised, and the gesture never reached `pointerup` to tear the resize down. Verified the test still fails when the listeners are moved back to the bubble phase, so it remains a real regression test. - DesktopShell.tsx: scope the "cannot occur during a real drag" claim to the pointer listener only. HTML5 drag-and-drop suppresses mouse events and a native drag holds an OS pointer grab, but neither spec promises keyboard events are withheld, and the inline comment on `onKeyDown` already assumed a keypress can arrive mid-drag. Escape is now described for what it is: a conventional request to cancel the stranded overlay or the drag itself. | 1 个月前 | |
test(e2e): fix the blend-modes flake and widen the per-commit gate (#2876) * test(e2e): fix the blend-modes flake and widen the per-commit gate Triage of the nightly E2E full failures (09-21, 09-22, 09-27, 10-03): every one but blend-modes was a real break already fixed on main (#2523, #2548, #2703, #2832). blend-modes needed a retry on 6 of the last 17 nights: it sampled its unblended reference pixel while the landing zoom's basemap tiles were still streaming in after the drop's fly-to. Wait for the polygon under the centre and an idle map first. Widen what gates a PR so those breaks fail their own PR: - layer-groups (Project -> Save -> reopen) and plugin-deep-link move to `core`. - New `core-engines` project: one smoke pass each for Cesium and ArcGIS (switch renderer, add GeoJSON, identify). It runs after `core` on one worker; beside the core workers on four CPUs it starved them and itself. - E2E_PORT overrides the preview port. Part of #2858 * Address review feedback - waitForCameraToLand could poll forever when a flight lands on the zoom it started from. The PR's own CI run hit a worse version of this: the Cesium globe intermittently refuses both the drop's fly-to-layer and a Set View to the dropped layer's centre (#2878), so the zoom never moved. Replace it with waitForSettledZoomNear, which waits for a target zoom by value, and park the camera with Set View on MapLibre before the engine swap so the pick needs no flight. The popup content is checked inside the click retry, since a late fly-to-layer closes an open popup. Part of #2858 | 12 小时前 | |
feat(notify): layer failure toasts on Cesium/ArcGIS, tile health, and notify follow-ups (#2911) * feat(notify): layer failure toasts on every renderer and notify follow-ups Map layers: Cesium and ArcGIS now report the failing store layer id in their diagnostic events, so a broken layer raises the same once-per-layer toast as on MapLibre/Mapbox. Cesium imagery providers count tile loads and failures; MapLibre tile 404s, which never become error events, are counted from the Diagnostics fetch capture. A 401/403 raises an "access refused" warning, and a layer whose first 12 tiles all fail with none loaded raises a "no tiles found" warning; sparse tile sets stay quiet. Converts the remaining silent catch blocks listed in #2885 where the user loses something (history load, autosave, window close, native open, KML re-read, project controls, plugin and point cloud restore, panel render, shared settings, initial catalog), and moves the import status, URL load error banners, and startup project warning onto notify. Part of #2869. * Address Claude review feedback - ArcGIS: getLayerLoadErrors maps store id to message, and the canvas keys reported layer failures by id, so two same-named layers failing the same way are each reported. - Autosave: a superseded attempt's late outcome no longer fires or resets the failure notice (new tracker isCurrent). - Drop status: dispose() takes its toasts down, as an unmount used to. * Address Claude review feedback - KML Super-Overlay: also notify when the resolver finds nothing without throwing (browser sessions, session-only keys), with a test. - Reuse the exported EMPTY_TILE_STATUSES instead of a second copy. - Drop status: clear the status slot directly instead of a dead show callback. | 4 小时前 | |
ci(fmt): add ruff + oxfmt auto-format workflow and config (#1327) * ci(fmt): add ruff + oxfmt auto-format workflow and config Adds a GitHub Actions workflow that runs ruff format/check on Python and notebooks and oxfmt on JS/TS/JSON/CSS/YAML/TOML, then pushes a style commit back to the branch. Mirrors the local pre-commit hooks so CI and local runs produce identical output. - .github/workflows/format.yml: pinned ruff==0.15.22, oxfmt@0.59.0 - ruff.toml: line-length 100, rules F/I/W/E, py310 target - .oxfmtrc.json: width 100, double quotes, lockfile/generated excludes - .pre-commit-config.yaml: local ruff + oxfmt hooks (offline-friendly) - .gitattributes: normalize all text to LF, explicit binary rules - docs/contributing.md: document the new formatting pipeline * ci(fmt): switch auto-format from GitHub Actions to pre-commit.ci pre-commit.ci authenticates via its own GitHub App, so the auto-format bot can push fixes back on fork PRs (where GITHUB_TOKEN is read-only). It only runs on PRs, so auto-formatted commits never bypass review. - Delete .github/workflows/format.yml - Add autofix/autoupdate config to .pre-commit-config.yaml; the ruff and oxfmt version pins there are now the single source of truth - Trim ignorePatterns comments in .oxfmtrc.json - Rewrite the coding-conventions section in docs/contributing.md * ci(fmt): address review feedback on ruff.toml and .gitattributes - .gitattributes: add explicit binary rules for *.icns, *.pbf, *.pmtiles (icon.icns, MapLibre glyph pbfs, osm.pbf fixtures, and mini.pmtiles are all tracked in-repo; closes the explicit-safety-net gap flagged in review) - ruff.toml: drop the misleading lines-after-imports = -1 (no-op default) and its comment; add .ruff_cache to extend-exclude so the intent survives any future change to ruff's default exclusion list - .gitignore: ignore .ruff_cache/ - .pre-commit-config.yaml: drop the redundant `files: \.(py|ipynb)$` regex (types_or already scopes the ruff hooks to python/pyi/jupyter) and fix the nbstripout hook indentation * ci(fmt): exclude generated whitebox-menu-catalog from oxfmt The file is auto-generated by scripts/gen-whitebox-menu-catalog.mjs (its header says 'do not hand-edit'), so add it to .oxfmtrc.json's ignorePatterns alongside the other generated catalogs. * ci(fmt): run ruff check --fix before ruff format Lint autofixes (import sorting via I, unused-import removal via F) can change layout; the formatter must run last so a single pre-commit pass stabilizes the file. Matches the order recommended by ruff's docs and astral-sh/ruff-pre-commit. * Update .gitattributes Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> * style: auto-format (ruff + oxfmt) [pre-commit.ci] * Address Claude review feedback - docs/contributing.md: note that oxfmt also sorts package.json keys into its conventional order, so contributors expect key reordering (not just whitespace changes) in future package.json diffs. * Address Claude review feedback - .oxfmtrc.json: drop the "python/**" ignore. It was a no-op for .py/.ipynb (the hook's types_or never passes them, and *.ipynb is excluded globally) while wrongly hiding real JS/TOML under python/ from oxfmt; the generated python/src/geolibre/static stays excluded via "**/static/**". Format the newly covered python/pyproject.toml and python/src/geolibre/_frontend.js. - ruff.toml: delete the [lint.pycodestyle] max-line-length block; it equaled the top-level line-length, which is already the default, so behavior is unchanged. Reworded the E-rules comment that pointed at it. - docs/contributing.md: restore the 2-space list-continuation indent on the mixed-line-ending bullet so the inline code span isn't split oddly. --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Qiusheng Wu <giswqs@gmail.com> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> | 2 个月前 | |
feat(field-collection): support multiple photo attachments (#2370) * feat(field-collection): support multiple photo attachments * fix(field-collection): wait for photo reads before saving | 21 天前 | |
ci(preview): smoke-test each deployed PR preview for a clean console (#2922) * ci(preview): smoke-test each deployed PR preview for a clean console Previews deploy to Cloudflare under /demo/, but nothing loaded the deployed app, and no spec failed on console errors. That is how #2916 (deployment.json 404) and #2919 (base-path fetches from the site root) reached web.geolibre.app and every preview unnoticed. - e2e/preview/: a config and spec that load PREVIEW_URL, wait for the MapLibre map to report loaded(), and fail on any console.error, page error, or same-origin 4xx/5xx. - pr-preview-deploy.yml: a smoke job runs it against the Cloudflare deployment from a default-branch checkout with a read-only token, and a smoke-status job posts the result as a commit status on the PR head, since workflow_run jobs do not appear in a PR's checks. - smoke.spec.ts (core): the same clean-console check against the local build on every PR (~3 s). - collectPageProblems and waitForMapLoaded shared in e2e/helpers.ts. * Address review feedback - collectPageProblems: always count the page's own document response. It can arrive before the frame commits its URL, so the same-origin check against page.url() dropped a 404'd app (Claude review). - preview config: collapse repeated slashes in PREVIEW_URL, so a deployment URL with a trailing slash plus "/demo/" still loads the app's directory (Claude review, CodeRabbit). - preview spec: attach and log the collected problems even when an earlier assertion fails first, so a 404'd app reports its status. - pr-preview-deploy.yml: document why smoke-status needs statuses: write (CodeRabbit / zizmor). | 52 分钟前 | |
test(e2e): pin identify-restore camera suppression in the core suite (#2500) (#2538) * test(e2e): pin identify-restore camera suppression in the core suite (#2500) Dismissing a resolved Identify popup restores the pre-Identify selection (#2498) and must not let "Zoom to selection" fit the camera to what it restored. That exemption rides on a read-once marker written by restoreIdentifySelection and read a commit later by MapCanvas's selection effect. tests/map-identify-lifecycle.test.ts pins the marker's exactly-once semantics, but nothing mounted a map to show the effect observing it in that window, so the guarantee rested on a manual camera-jump check. Adds e2e/identify-restore.spec.ts to the core project, driving the built web app: - Select a feature on one layer, identify a feature on another, dismiss with the popup's close button: the original layer and feature come back and the camera stays on the identified feature. - Change the selection while the popup is open: dismissal leaves that pick alone, camera included. - Click past the features: the Identify result clears, the identified layer stays selected, nothing is restored. This is the documented MapLibre/Mapbox parity behavior from #2498, recorded here so the difference from the close button is a choice the suite states. Supporting changes: two GeoJSON fixtures (one wide polygon, two small squares inside it, so a stray re-fit is a large camera move rather than a rounding difference) and a bindMapLibreMap helper that reaches the live map through the React fiber, the MapLibre counterpart of the bindMapboxMap the Mapbox specs already use. Verified against the negative case: stubbing consumePendingIdentifyRestore to always return false fails the first test with zoom 3.31 where 6.72 was expected, so the spec guards the real regression. * Address Claude review feedback - Name the miss-click's 12 px canvas inset as MISS_CLICK_INSET and say why that value is safe: it sits inside the 40 px FIT_BOUNDS_PADDING a fit reserves, so the just-fitted square cannot reach it. It read as an unexplained magic number relative to the padding it depends on. * Address Claude and CodeRabbit review feedback - Count camera movements instead of sampling positions across a dismissal. waitForCameraIdle's five still polls could in principle resolve before a delayed fit even started on a loaded runner, so the position comparison had a residual window in which a re-fit could pass unseen. A movestart counter, reset just before the dismissal, answers "did the camera move at all since then" and closes that window. It also reports the regression more sharply: with the suppression stubbed out the failure is now "Expected 0, Received 2" rather than a zoom delta. - Anchor the miss-click to the identified square's own projected west edge rather than to an inset calibrated against FIT_BOUNDS_PADDING, which is module-private in map-controller.ts and linked only by comment. The gap now holds whatever that constant becomes, and an explicit assertion on the projected edge says so if a future fit leaves no room west of it. - Check every method of TestMapHandle before accepting a candidate in bindMapLibreMap. The fiber walk met many hook values and probed only three of the methods the specs go on to call, so a partial value could have been stashed and thrown later from an unrelated line. * Address Claude review feedback on the miss-click anchor - Take the miss-click's y from the projected west-edge point too, not from the canvas's vertical centre, so both coordinates are anchored to the feature rather than one to the feature and one to the viewport. The click was already a guaranteed miss (north-up camera, so the square projects axis-aligned and anything west of its west edge is outside it at any latitude), but the mixed anchors left that needing an argument rather than being evident; the comment now states the north-up premise. --------- Co-authored-by: giswqs <5016453+giswqs@users.noreply.github.com> | 12 天前 | |
fix(a11y): un-nest layer rows and broaden the axe sweep (#2895) * fix(a11y): un-nest layer rows and broaden the axe sweep The layer panel rendered each row as a role="button" card wrapping its own controls, a serious axe nested-interactive finding the a11y specs allowlisted. Rows, group headers and the Background card are now listitems in a labelled list, carrying their depth as aria-level, and selection moves to a dedicated name button with aria-pressed. Both allowlists (a11y.spec.ts, rtl.spec.ts) are gone. A new nightly a11y-screens.spec.ts scans the Add Data panels and dialogs, the Processing toolbox, the Style panel for a vector and a raster layer, every Settings section, New Project with Examples, Project History, Print Layout, Manage Plugins, and an error toast. It found and this fixes: unnamed selects in Geocoding settings and the Whitebox toolbox, and the raster panel's faint text at 3.5:1. keyboard-only.spec.ts (core) adds an OGC API - Features layer by URL, changes its circle radius, and exports it as GeoJSON with no pointer input. The shared axe helper waits for animations so a mid-fade frame is not reported as a contrast failure. Part of #2858 * Address review feedback - LayerRow: F2 on a layer's name button starts a rename, the keyboard counterpart of the double-click (CodeRabbit), with a component test. - a11y-screens.spec.ts: slugify the Add Data attachment labels. - a11y.ts: bound the animation wait at 10 s so a stuck animation fails with a clear error. - keyboard-only.spec.ts: tabTo treats a not-yet-rendered target as not focused instead of stalling on the default action timeout. - layer-panel.spec.ts: update a comment that still described the card as a role="button" wrapper (Claude review). * Address Claude review feedback - Drop the data-layer-card markers on the layer and Background cards: their only consumer was the nested-interactive allowlist this PR removes. | 6 小时前 | |
ci(fmt): add ruff + oxfmt auto-format workflow and config (#1327) * ci(fmt): add ruff + oxfmt auto-format workflow and config Adds a GitHub Actions workflow that runs ruff format/check on Python and notebooks and oxfmt on JS/TS/JSON/CSS/YAML/TOML, then pushes a style commit back to the branch. Mirrors the local pre-commit hooks so CI and local runs produce identical output. - .github/workflows/format.yml: pinned ruff==0.15.22, oxfmt@0.59.0 - ruff.toml: line-length 100, rules F/I/W/E, py310 target - .oxfmtrc.json: width 100, double quotes, lockfile/generated excludes - .pre-commit-config.yaml: local ruff + oxfmt hooks (offline-friendly) - .gitattributes: normalize all text to LF, explicit binary rules - docs/contributing.md: document the new formatting pipeline * ci(fmt): switch auto-format from GitHub Actions to pre-commit.ci pre-commit.ci authenticates via its own GitHub App, so the auto-format bot can push fixes back on fork PRs (where GITHUB_TOKEN is read-only). It only runs on PRs, so auto-formatted commits never bypass review. - Delete .github/workflows/format.yml - Add autofix/autoupdate config to .pre-commit-config.yaml; the ruff and oxfmt version pins there are now the single source of truth - Trim ignorePatterns comments in .oxfmtrc.json - Rewrite the coding-conventions section in docs/contributing.md * ci(fmt): address review feedback on ruff.toml and .gitattributes - .gitattributes: add explicit binary rules for *.icns, *.pbf, *.pmtiles (icon.icns, MapLibre glyph pbfs, osm.pbf fixtures, and mini.pmtiles are all tracked in-repo; closes the explicit-safety-net gap flagged in review) - ruff.toml: drop the misleading lines-after-imports = -1 (no-op default) and its comment; add .ruff_cache to extend-exclude so the intent survives any future change to ruff's default exclusion list - .gitignore: ignore .ruff_cache/ - .pre-commit-config.yaml: drop the redundant `files: \.(py|ipynb)$` regex (types_or already scopes the ruff hooks to python/pyi/jupyter) and fix the nbstripout hook indentation * ci(fmt): exclude generated whitebox-menu-catalog from oxfmt The file is auto-generated by scripts/gen-whitebox-menu-catalog.mjs (its header says 'do not hand-edit'), so add it to .oxfmtrc.json's ignorePatterns alongside the other generated catalogs. * ci(fmt): run ruff check --fix before ruff format Lint autofixes (import sorting via I, unused-import removal via F) can change layout; the formatter must run last so a single pre-commit pass stabilizes the file. Matches the order recommended by ruff's docs and astral-sh/ruff-pre-commit. * Update .gitattributes Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> * style: auto-format (ruff + oxfmt) [pre-commit.ci] * Address Claude review feedback - docs/contributing.md: note that oxfmt also sorts package.json keys into its conventional order, so contributors expect key reordering (not just whitespace changes) in future package.json diffs. * Address Claude review feedback - .oxfmtrc.json: drop the "python/**" ignore. It was a no-op for .py/.ipynb (the hook's types_or never passes them, and *.ipynb is excluded globally) while wrongly hiding real JS/TOML under python/ from oxfmt; the generated python/src/geolibre/static stays excluded via "**/static/**". Format the newly covered python/pyproject.toml and python/src/geolibre/_frontend.js. - ruff.toml: delete the [lint.pycodestyle] max-line-length block; it equaled the top-level line-length, which is already the default, so behavior is unchanged. Reworded the E-rules comment that pointed at it. - docs/contributing.md: restore the 2-space list-continuation indent on the mixed-line-ending bullet so the inline code span isn't split oddly. --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Qiusheng Wu <giswqs@gmail.com> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> | 2 个月前 | |
fix: import local CSV and Excel tables from layer joins (#2267) * fix: import local CSV and Excel tables from layer joins Allow users to import non-spatial tables directly in the Joins form, even when no source layer exists. Preserve the target selection and embed imported rows in the project. * Address Claude review feedback - Translate the "CSV / Excel" label in JoinTableFileInput: add `addData.delimitedText.fileFilter` to all 19 locale catalogs (following the `addData.cad.fileFilter` precedent) and use it for both the visible hint and the file-dialog filter name, which was hardcoded too. - Build the imported join table with `createBaseLayer` and no vector styling data instead of `addGeoJsonLayer` + `updateLayer`, so a null-geometry table no longer reserves a palette color it never draws with — matching the attribute-table path in DelimitedTextSource. Also collapses the import to a single store mutation. - Drop the now-orphaned `style.joins.noCandidates` key from every locale catalog; its last reference went away with the always-enabled "Add join" button. * Address CodeRabbit review feedback - Widen the join-table file filter label from "CSV / Excel" to "CSV / TSV / TXT / Excel" in all 19 locale catalogs: the chooser accepts .csv/.tsv/.txt/.xls/.xlsx, so the old label undersold two of the formats in both the dialog filter and the visible hint. The value is format names only, so it is identical in every catalog. * Address Claude review feedback - Reuse `layerNameFromPath`/`fileNameFromPath` from the Add Data helpers instead of re-deriving the base name by hand at two call sites, so all three "strip directory, strip extension" sites stay in sync. This also gives the layer the shared default name when a file is all extension, where the hand-rolled version produced an empty name. - Restore the feature selection after re-selecting the join target: `addLayer` selects the new table and `selectLayer` clears `selectedFeatureId`/ `selectedFeatureIds`, so importing a join table used to silently drop the user's map/attribute-table selection even though the target layer never changed. | 28 天前 | |
fix(a11y): un-nest layer rows and broaden the axe sweep (#2895) * fix(a11y): un-nest layer rows and broaden the axe sweep The layer panel rendered each row as a role="button" card wrapping its own controls, a serious axe nested-interactive finding the a11y specs allowlisted. Rows, group headers and the Background card are now listitems in a labelled list, carrying their depth as aria-level, and selection moves to a dedicated name button with aria-pressed. Both allowlists (a11y.spec.ts, rtl.spec.ts) are gone. A new nightly a11y-screens.spec.ts scans the Add Data panels and dialogs, the Processing toolbox, the Style panel for a vector and a raster layer, every Settings section, New Project with Examples, Project History, Print Layout, Manage Plugins, and an error toast. It found and this fixes: unnamed selects in Geocoding settings and the Whitebox toolbox, and the raster panel's faint text at 3.5:1. keyboard-only.spec.ts (core) adds an OGC API - Features layer by URL, changes its circle radius, and exports it as GeoJSON with no pointer input. The shared axe helper waits for animations so a mid-fade frame is not reported as a contrast failure. Part of #2858 * Address review feedback - LayerRow: F2 on a layer's name button starts a rename, the keyboard counterpart of the double-click (CodeRabbit), with a component test. - a11y-screens.spec.ts: slugify the Add Data attachment labels. - a11y.ts: bound the animation wait at 10 s so a stuck animation fails with a clear error. - keyboard-only.spec.ts: tabTo treats a not-yet-rendered target as not focused instead of stalling on the default action timeout. - layer-panel.spec.ts: update a comment that still described the card as a role="button" wrapper (Claude review). * Address Claude review feedback - Drop the data-layer-card markers on the layer and Background cards: their only consumer was the nested-interactive allowlist this PR removes. | 6 小时前 | |
fix(lidar): restore auto-zoom on load and draw measure lines over point clouds (#2536) * fix(lidar): restore auto-zoom on load and draw measure lines over point clouds Two LiDAR bugs reported together. **Loading a point cloud stopped flying to it.** `maplibre-gl-lidar` starts a `fitBounds` when `autoZoom` is on, then emits `load` — from which GeoLibre takes the deck.gl overlays' shared mercator lock and flips the projection preference. `MapController.applyMapPreferences` re-applied the current camera to clamp it to the newly installed constraints, and that jump *stops* an animation in flight, pinning the camera at the world view the fly-to had just left. Only the first load of a session was affected, because a second one finds the preference already mercator and changes nothing, which made it look like a plugin bug rather than a camera one. The clamp now waits for `moveend` when the camera is moving; the min/max zoom, pitch and bounds setters already constrain the animation's own target in the meantime. **A measured line was invisible inside a point cloud (#2533).** The cloud is drawn into an overlaid deck.gl canvas above MapLibre's, and the two cannot interleave — interleaving deck into MapLibre's canvas does not help either, since MapLibre pins translucent 2D layers near the far plane while deck writes real point depths. So redraw the measure geometry into the point cloud's *own* deck overlay with `depthTest: false`, which is how the plugin's own cross-section line already manages to sit above the points. The MapLibre layers are left alone: outside the cloud both copies draw the same geometry in the same colour (now passed to the control explicitly so the two cannot drift), and inside it the deck copy is the one that shows. Deck paints in array order, so the mirror re-appends itself on any frame where streaming has dropped a new chunk layer on top of it. Both are also covered end to end (`e2e/lidar-auto-zoom.spec.ts` asserts the camera reaches the cloud; `e2e/lidar-measure-mirror.spec.ts` reads blue line pixels back out of the deck canvas), and the mirrored upstream internals are written up in docs/maintenance.md. Fixes #2533 * Address review feedback - Read the measure geometry from both engines' geojson sources. mapbox-gl stores the value handed to `setData` in `_data` directly while maplibre-gl v6 wraps it as `{ geojson }`, so reading only the wrapper left the mirror silently dead on the Mapbox renderer — indistinguishable from "nothing measured yet". Both shapes are unwrapped now, and the unit test drives the mirror against each. (CodeRabbit, claude-review) - Warn once per attachment when the source cannot be read at all, matching the loud-on-drift handling `measureSourceId` already has for its own private field. (claude-review) - Serve the COPC the LiDAR specs load from a vendored 33 KB fixture instead of PDAL's mutable `master` on raw.githubusercontent.com: the specs assert an exact point count, so an upstream edit — or an offline runner — would fail them with no product regression. `serveCopcFixture` answers the byte ranges a COPC reader asks for, so the streaming path is still the one under test. (CodeRabbit, claude-review) * Address Claude review feedback - Treat a measure source that holds no data as "not ready" rather than as an unreadable shape, so the warn-once path cannot fire on ordinary "nothing measured yet" usage. The upstream control does seed an empty FeatureCollection today, so this is only reachable if that changes — which is not the drift the warning is there to catch. Covered both ways in the unit test. * Address Claude review feedback - Guard `keepOnTop`'s `getLayers()` call. It runs from a map `render` listener, so a torn-down overlay throwing there would escape into the render loop — a far worse failure than the "line stays hidden" the rest of the module is built around. It now gives up on the mirror the way `place` does. - Treat `null` source data as "not ready" alongside `undefined`: maplibre's wrapper can carry it, and falling through to the unreadable-shape warning was an unintended asymmetry. * Load the E2E point cloud from a pinned URL instead of a vendored fixture Keeps the binary out of the repository. The stability concern the fixture was answering — the specs assert an exact point count, so an edit on PDAL's `master` would fail them with no product regression — is handled by pinning the raw URL to the commit that holds the file instead (byte-identical to what was vendored). The `features` suite already loads remote data in several specs, so this is the same dependency they carry. | 12 天前 | |
fix(lidar): stop point clouds from covering map control panels (#2532) * fix(lidar): stop point clouds from covering map control panels maplibre-gl-lidar renders point clouds into an overlaid deck.gl canvas. That canvas is a MapLibre `IControl`, so MapLibre appended its absolutely positioned, map-sized wrapper to a corner of `.maplibregl-control-container`, where every sibling is statically positioned -- the wrapper therefore painted above any control panel it overlapped, and above every Marker (those live further down, in the canvas container). Loading a point cloud buried the Measure, Colorbar, Legend, HTML and Bookmark panels, plus the measure tool's vertex handles. maplibre-gl-lidar 0.17.1 moves the wrapper into the canvas container, directly after the base map canvas, and tags it `maplibre-gl-lidar-canvas`. The space effects engine raises the map canvas to z-index 4 so its starfield canvases can sit underneath, which would leave the untagged wrapper below the basemap and hide the point cloud outright, so give it that same z-index: equal z-index plus a later DOM position keeps it above the basemap, while markers and the control container -- both one step higher -- stay above the points. The overlay CSS moves into an exported `effectsOverlayCss()` so the ordering is assertable without a DOM. Fixes #2530 * Address review feedback - Document the mirrored `maplibre-gl-lidar-canvas` DOM contract in docs/maintenance.md, the page CLAUDE.md points a maplibre-gl-* bump at, so a future bump (Dependabot included) re-checks the class name and placement. - Build the unit test's selector from the package's own exported DECK_CANVAS_CLASS instead of a hand-written literal, so a rename upstream fails the test rather than silently un-fixing the stacking. - Add e2e/lidar-canvas-stacking.spec.ts: mount the real LiDAR control and assert the resulting DOM order and z-indices, which the CSS-text test cannot see. No point cloud is loaded, since the stacking is a DOM property. * Address Claude review feedback - Import DECK_CANVAS_CLASS from maplibre-gl-lidar instead of re-declaring the literal, so a rename upstream fails `npm run typecheck` rather than silently un-fixing the stacking. Checked the bundle concern rather than assuming: the package is side-effect-free, the import tree-shakes to the string, and the chunk set and sizes are unchanged. - The e2e spec keeps the spelled-out selector, with a note why: Playwright transpiles specs to CJS and this package's CJS entry requires @deck.gl/maplibre, which publishes no CJS export. - Reworked the docs/maintenance.md entry accordingly: the class is now compiler-checked, and only the placement needs the e2e run. | 12 天前 | |
fix(lidar): restore auto-zoom on load and draw measure lines over point clouds (#2536) * fix(lidar): restore auto-zoom on load and draw measure lines over point clouds Two LiDAR bugs reported together. **Loading a point cloud stopped flying to it.** `maplibre-gl-lidar` starts a `fitBounds` when `autoZoom` is on, then emits `load` — from which GeoLibre takes the deck.gl overlays' shared mercator lock and flips the projection preference. `MapController.applyMapPreferences` re-applied the current camera to clamp it to the newly installed constraints, and that jump *stops* an animation in flight, pinning the camera at the world view the fly-to had just left. Only the first load of a session was affected, because a second one finds the preference already mercator and changes nothing, which made it look like a plugin bug rather than a camera one. The clamp now waits for `moveend` when the camera is moving; the min/max zoom, pitch and bounds setters already constrain the animation's own target in the meantime. **A measured line was invisible inside a point cloud (#2533).** The cloud is drawn into an overlaid deck.gl canvas above MapLibre's, and the two cannot interleave — interleaving deck into MapLibre's canvas does not help either, since MapLibre pins translucent 2D layers near the far plane while deck writes real point depths. So redraw the measure geometry into the point cloud's *own* deck overlay with `depthTest: false`, which is how the plugin's own cross-section line already manages to sit above the points. The MapLibre layers are left alone: outside the cloud both copies draw the same geometry in the same colour (now passed to the control explicitly so the two cannot drift), and inside it the deck copy is the one that shows. Deck paints in array order, so the mirror re-appends itself on any frame where streaming has dropped a new chunk layer on top of it. Both are also covered end to end (`e2e/lidar-auto-zoom.spec.ts` asserts the camera reaches the cloud; `e2e/lidar-measure-mirror.spec.ts` reads blue line pixels back out of the deck canvas), and the mirrored upstream internals are written up in docs/maintenance.md. Fixes #2533 * Address review feedback - Read the measure geometry from both engines' geojson sources. mapbox-gl stores the value handed to `setData` in `_data` directly while maplibre-gl v6 wraps it as `{ geojson }`, so reading only the wrapper left the mirror silently dead on the Mapbox renderer — indistinguishable from "nothing measured yet". Both shapes are unwrapped now, and the unit test drives the mirror against each. (CodeRabbit, claude-review) - Warn once per attachment when the source cannot be read at all, matching the loud-on-drift handling `measureSourceId` already has for its own private field. (claude-review) - Serve the COPC the LiDAR specs load from a vendored 33 KB fixture instead of PDAL's mutable `master` on raw.githubusercontent.com: the specs assert an exact point count, so an upstream edit — or an offline runner — would fail them with no product regression. `serveCopcFixture` answers the byte ranges a COPC reader asks for, so the streaming path is still the one under test. (CodeRabbit, claude-review) * Address Claude review feedback - Treat a measure source that holds no data as "not ready" rather than as an unreadable shape, so the warn-once path cannot fire on ordinary "nothing measured yet" usage. The upstream control does seed an empty FeatureCollection today, so this is only reachable if that changes — which is not the drift the warning is there to catch. Covered both ways in the unit test. * Address Claude review feedback - Guard `keepOnTop`'s `getLayers()` call. It runs from a map `render` listener, so a torn-down overlay throwing there would escape into the render loop — a far worse failure than the "line stays hidden" the rest of the module is built around. It now gives up on the mirror the way `place` does. - Treat `null` source data as "not ready" alongside `undefined`: maplibre's wrapper can carry it, and falling through to the unreadable-shape warning was an unintended asymmetry. * Load the E2E point cloud from a pinned URL instead of a vendored fixture Keeps the binary out of the repository. The stability concern the fixture was answering — the specs assert an exact point count, so an edit on PDAL's `master` would fail them with no product regression — is handled by pinning the raw URL to the commit that holds the file instead (byte-identical to what was vendored). The `features` suite already loads remote data in several specs, so this is the same dependency they carry. | 12 天前 | |
feat(mapbox): host Zarr layers on the Mapbox renderer (#2414) * feat(mapbox): host Zarr layers on the Mapbox renderer @carbonplan/zarr-layer is a CustomLayerInterface implementation that targets Mapbox GL as well as MapLibre, and the Zarr control only uses the style API both engines share, so the panel needed no adapter — only the gates that assumed a MapLibre map: - isMapboxPluginLayer recognises zarr-url store layers (no engine error, no "No Mapbox" badge) - supportsAddDataRenderer no longer withholds zarr on Mapbox - the untiled NetCDF/Kerchunk Zarr path locks Mercator on either 2D engine - STAC Zarr assets are addable on Mapbox; the NetCDF dialog drops its Mapbox-only image/unsupported-reference branches Adds e2e/mapbox-zarr.spec.ts (routed mini store, runs without a token) and updates the unit tests and docs. * Address Claude review feedback - NetCDF dialog: a remote NetCDF/HDF cube with a time axis now renders its selected slice as an image on both engines. The remote reader has no layer-refs builder, so the non-image branch had nothing for it and submit silently added no layer (pre-existing on MapLibre; reachable on Mapbox once the Mapbox-only image escape hatch was removed). - STAC: inline `canAddAsset` at its four call sites and drop the `canAddAssetToMap` wrapper, which no longer carried any renderer logic. * Address CodeRabbit review feedback - docs: state that the NetCDF/HDF and Kerchunk cube path locks the map to Mercator when a layer is added (the untiled renderer draws in Web Mercator), while Zarr Layer and STAC adds keep the current projection. * Address review feedback and update the Mapbox menu specs for Zarr - e2e: mapbox-deck-overlays.spec.ts and mapbox-add-data.spec.ts expected "Zarr Layer" to stay disabled on Mapbox; it is enabled now, so the entry moves to the enabled list (the CI E2E failure on this branch). - docs: the Mercator statement is limited to local NetCDF/HDF cubes and Kerchunk references; a remote NetCDF/HDF URL renders an image overlay (CodeRabbit). - docs: the Zarr validation row says pixel output was not confirmed here, so the gap noted in the PR survives the merge (Claude review). | 18 天前 | |
feat: support PMTiles, LiDAR and 3D Tiles on Mapbox (#2393) * feat: support PMTiles, LiDAR and 3D Tiles on Mapbox Enable archive and 3D imports through native Mapbox sources and deck.gl overlays, preserving layers across project reloads and renderer switches. Default new Mapbox projects to Standard and cover the supported paths with real-data browser tests. * fix: sample Mapbox tile attributes for PMTiles extrusion The attribute backfill only queried MapLibre sources, leaving Mapbox extrusion height selectors empty. Query each renderer’s native source and share the loaded attributes with style classification and quick filters. * fix: apply Background opacity to Mapbox Standard Prepare Standard as a local import with native opacity expressions so ocean labels, 3D objects and atmosphere fade with the Background card. Preserve other basemaps and retry visibility updates while the style is loading. * Address review feedback - Keep Mapbox 3D Tiles revision tracking at module scope so re-running restoreMapboxTiles (every panel add) no longer renumbers loaded tilesets, which deck.gl treated as new layers and reloaded. - Guard Tile3DLayer callbacks by a session-unique revision token so a finalized instance's late onTilesetLoad/onError/onTileError cannot update the store or consume a fly-to request meant for its replacement. - Let a blank tileset URL on Mapbox fall through to the library's own submit handler so its "Tileset URL is required." error is shown. - Serialize Mapbox PMTiles additions with a per-request input snapshot so overlapping calls no longer overwrite each other's loading/error state and addPMTilesLayerFromUrl reads its own outcome. - Detach the LiDAR load listener with the torn-down control so a restore still streaming through an engine switch cannot land as a duplicate layer; the saved record is re-streamed by the next engine instead. - Tilt the camera to at least 60° when zooming to a 3D Tiles layer on Mapbox, matching MapController.fitLayer. * Address review feedback - Catch rejections from the fire-and-forget restoreMapboxTiles calls so a failed deck.gl chunk load logs instead of surfacing as an unhandled rejection, matching the restoreLidarLayers call in DesktopShell. - Raise the fake camera pitch before the second fitLayer assertion so the test proves the steeper pitch is preserved, not just that 60 is set. | 19 天前 | |
test(e2e): budget the renderer swap so the DuckDB spec passes first try (#2437) * test(e2e): budget the renderer swap so the DuckDB spec passes first try `switchRenderer` clicked the rendering-engine radio item under the spec's 30 s `actionTimeout`, but the swap is not a cheap click: `setPrimaryRenderer` is a discrete React update, so the outgoing engine's `map.remove()` and, on the way back to MapLibre, `new maplibregl.Map()` all run inside that click's own handler. Playwright does not resolve `click` until the browser acknowledges the input event, so the whole swap is charged against the action budget. With a DuckDB query layer drawn, the deck overlay's buffers go down with the old context, and on CI's software renderer that outran 30 s on every first attempt; `retries: 1` was what made the spec green. Give the swap its own budget for the radio click and the canvas assertion so the first attempt tests the path instead of leaving the retry to do the work. * test(e2e): name the renderer-swap budget * test(e2e): share the renderer-swap budget across the mapbox specs `switchRenderer` is copied verbatim into three mapbox specs, all under the same 30 s `actionTimeout`, so the swap that outran the budget in the DuckDB spec can outrun it in the Geo Editor and Overture ones too. Move the budget and the explanation to one exported constant in `helpers.ts` and use it in all three, so the copies cannot drift on the number that matters. --------- Co-authored-by: giswqs <5016453+giswqs@users.noreply.github.com> | 17 天前 | |
fix(map): keep Identify cursor consistent across renderers (#2495) Identify only updated individual canvases, leaving renderer containers with their default grab cursor. Synchronize cursor ownership across every engine and cover activation, popup, selection handoff, and teardown paths. | 15 天前 | |
feat(flight-simulator): fly the Mapbox renderer's free camera (#2425) * feat(flight-simulator): fly the Mapbox renderer's free camera MapLibre dropped Mapbox's `setFreeCameraOptions` and replaced it with `calculateCameraOptionsFromCameraLngLatAltRotation`, which is how the simulator has placed the camera on the 2D map; a mapbox-gl map has neither that helper nor `setCenterClampedToGround`. It still has the free camera, so the plugin gains a third adapter that writes the aircraft straight into it: a `MercatorCoordinate` carrying the altitude plus `setPitchBearing`, from which mapbox-gl derives the same center and zoom the MapLibre helper computes by hand. Center clamping has no counterpart and needs none — the free camera positions the eye, not the map center. The flight model, keyboard, HUD and animation loop are unchanged. `flightTarget` now returns a tagged handle so the three engines pick their own adapter, and the plugin declares `engines: ["maplibre", "mapbox", "cesium"]`. Terrain is read through `app.isTerrainEnabled()` rather than `map.getTerrain()`: the Mapbox Standard style imports its own terrain, so the map reports terrain while GeoLibre's DEM is off, and the flight would measure its height above a DEM it never turned on. Each camera write carries the flight token, and `MapboxCanvas` skips tagged `moveend` events the way `MapCanvas` does — without it the store's saved view would be overwritten ~60 times a second. One setting cannot work here: mapbox-gl 3 has no camera roll axis at all, so "Bank the horizon in turns" leaves the horizon level (the aircraft still banks, and a bank still turns it); the panel says so while Mapbox is primary. Also guards the Atmospheric Effects halo against a non-finite disc. A steeply pitched globe can project a silhouette sample behind the horizon, and `createRadialGradient` throws on that, taking the effects render loop down — reproducible on Mapbox with a bare `jumpTo({pitch: 78})`, and hit every frame once a flight is in the air. Verified in the browser on a `primaryRenderer: "mapbox"` project in both themes (terrain on/off round trip, altitude above the queried DEM, arrow-key bank and turn, zero console errors, full restore on Esc) and covered by e2e/mapbox-flight-simulator.spec.ts plus unit tests for the new adapter. Part of #2420 (Flight Simulator item). * test(e2e): make the Mapbox flight spec survive a tokenless CI runner Two CI-only failures, neither about the feature. The spec asserted no console errors at all. CI has no MAPBOX_TOKEN, so every request to api.mapbox.com — the DEM the simulator flies over among them — comes back 401/403 and the browser logs a bare "Failed to load resource" for each. Assert on app errors instead; the flight itself is checked through the map's own camera and state, which those requests do not affect. And the bank assertion held Arrow Right for a fixed 2s and expected a fixed angle. The turn is integrated per animation frame, and a software-WebGL runner draws far fewer of them, so it hold the key until the heading actually moves. The movement and moveend assertions get the same treatment. * review: name the roll axis in four locales, tighten the e2e failure filter - ar/fa/ka/vi said "camera rotation", which is broader than the English "camera roll" and could read as Mapbox having no bearing or pitch either. Name the axis explicitly in each (CodeRabbit). - The e2e dropped every "Failed to load resource" console line, which would also hide a genuinely broken app asset. Record failed responses with their URL and status instead, and ignore only 401/403 from the Mapbox API hosts — the ones a tokenless CI run is expected to produce. requestfailed is handled the same way (CodeRabbit). - docs/architecture.md said these plugins "declare both engines" in a sentence that now covers three (CodeRabbit). * review: do not count a cancelled tile as a failed request Every pan and projection switch abandons the requests for the view it left, and `requestfailed` fires for those aborts too. A spec that drives the camera would report them as failures on a slower runner. | 18 天前 | |
test(e2e): budget the renderer swap so the DuckDB spec passes first try (#2437) * test(e2e): budget the renderer swap so the DuckDB spec passes first try `switchRenderer` clicked the rendering-engine radio item under the spec's 30 s `actionTimeout`, but the swap is not a cheap click: `setPrimaryRenderer` is a discrete React update, so the outgoing engine's `map.remove()` and, on the way back to MapLibre, `new maplibregl.Map()` all run inside that click's own handler. Playwright does not resolve `click` until the browser acknowledges the input event, so the whole swap is charged against the action budget. With a DuckDB query layer drawn, the deck overlay's buffers go down with the old context, and on CI's software renderer that outran 30 s on every first attempt; `retries: 1` was what made the spec green. Give the swap its own budget for the radio click and the canvas assertion so the first attempt tests the path instead of leaving the retry to do the work. * test(e2e): name the renderer-swap budget * test(e2e): share the renderer-swap budget across the mapbox specs `switchRenderer` is copied verbatim into three mapbox specs, all under the same 30 s `actionTimeout`, so the swap that outran the budget in the DuckDB spec can outrun it in the Geo Editor and Overture ones too. Move the budget and the explanation to one exported constant in `helpers.ts` and use it in all three, so the copies cannot drift on the number that matters. --------- Co-authored-by: giswqs <5016453+giswqs@users.noreply.github.com> | 17 天前 | |
feat(plugins): dock GeoAgent in the side panel and follow the dark theme (#2841) * feat(plugins): dock GeoAgent in the side panel and follow the dark theme GeoAgent floated over the map as a MapLibre control with its own toolbar button and position submenu. It now opens in GeoLibre's dockable side panel (sharing the Style sidebar), mounted through the same dock bridge as Overture and USGS LiDAR. The control is built per render so it always targets the renderer that is drawing the map, and the chunk-load rollback and activation-generation guard are kept. The upstream stylesheet is light-only, so the panel, its custom select menu and the injected Earth Engine sign-in button are remapped onto the app's design tokens under .dark. In the dock the control's own title, close button and resize handles are hidden, and the conversation log keeps a minimum height so the panel scrolls instead of squeezing it. The Mapbox GeoAgent E2E spec reached the control through the engine's plugin-control map; it now captures it by rewriting the GeoAgent chunk in page.route, with service workers blocked so the route sees it. * Address review feedback - Release GeoAgent's store sync and Layers rows when the map itself is removed: the dock bridge gains an onMapRemove callback, since a swap to an engine that never mounts (Mapbox with no token) neither closes the panel nor re-activates the plugin (CodeRabbit). - Drop the hardcoded position option; upstream reads it only for the floating panel's inline placement, which the dock overrides (Claude). - Drop the unreachable .dark .geoagent-panel-title rule and note that the header icon-button selector assumes close is its only icon button (Claude). | 1 天前 | |
fix(plugins): keep docked plugins active across a renderer swap (#2832) Nightly E2E full failed on the Mapbox Overture renderer-swap spec. Two real bugs surfaced once Overture moved into the dockable panel: - The dock bridge called control.onRemove on the map's own remove event, after the engine had torn the canvas down, so the control threw and the map showed 'Map failed to render'. A throw during map removal is now tolerated. - A plugin whose deactivate closes its docked panel scheduled a deferred deactivation that ran after the swap had re-activated it, leaving the plugin off. The deferred deactivation now skips a stale activation. The spec is updated for the docked panel (no Activate submenu) and the collapsed-on-restore panel. | 1 天前 | |
fix: restore raster layers on Mapbox (#2479) | 15 天前 | |
feat(streetview): run Street View on the Mapbox renderer (#2426) * feat(streetview): run Street View on the Mapbox renderer Everything `maplibre-gl-streetview` touches is on the Style Spec surface both 2D engines share — `getContainer`, `on`/`off`, a click event's `lngLat`, and `LngLat` only as a value carrier — except the location marker. MapLibre's `Marker` reads `map._camera.transform` on every position update, which a mapbox-gl map does not have, so the first map click threw there. maplibre-gl-streetview 0.8.0 takes a `createMarker` factory: the control still builds, styles and rotates its own marker element, and only the positioning comes from the engine. On a Mapbox host the plugin feeds it mapbox-gl's `Marker` through `app.getMapboxGl()`; on MapLibre it passes nothing and the upstream default (MapLibre's own `Marker`) stands. A renderer swap tears every active plugin down and re-activates it, so the rebuilt control picks up the factory of whichever engine is now primary. Verified in the browser on a `primaryRenderer: "mapbox"` project: the Plugins menu no longer greys the entry out, the control mounts in the Mapbox control corner, a map click places a `.mapboxgl-marker` (and no maplibre marker) that tracks the map through pans, Mapillary imagery loads in the panel, and a swap back to MapLibre rebuilds with MapLibre's marker — all with zero console errors. Covered by e2e/mapbox-streetview.spec.ts, which feeds the control a key through its own Keys tab and mocks the provider so it needs no Street View account, plus unit tests for the factory. Part of #2420 (Street View item). Upstream: opengeos/maplibre-gl-streetview#16, v0.8.0. * test(e2e): ignore tokenless 401s in the Mapbox Street View spec The spec asserted no console errors at all. CI has no MAPBOX_TOKEN, so every request to api.mapbox.com comes back 401/403 and the browser logs a bare "Failed to load resource" for each. That says nothing about the marker path under test, which is asserted through the DOM the control produces — the marker is mapbox-gl's, there is no maplibre one, and it tracks the map. * review: decide the marker engine on the renderer, not the namespace CodeRabbit spotted a real race. `getMapboxGl()` only answers once MapboxEngine has mounted, but the Plugins menu and PluginManager.supportsEngine both gate on the store's `primaryRenderer`, which flips synchronously. A plugin activated in that window got `undefined`, kept MapLibre's Marker for the control's whole lifetime, and threw on the first map click — the exact bug this PR is about. Commit on the renderer instead, and read the namespace when a marker is actually built: that happens inside the control's onAdd, by which point the map it is being added to necessarily exists. The unreachable case throws rather than silently placing a marker that would fail later with a confusing message. Also replaces the e2e's console-text filter, which dropped every "Failed to load resource" line and would have hidden a genuinely broken asset. Failed responses are now recorded with their URL and status, and only 401/403 from the Mapbox API hosts are ignored. * review: do not count a cancelled tile as a failed request Every pan and projection switch abandons the requests for the view it left, and `requestfailed` fires for those aborts too. A spec that drives the camera would report them as failures on a slower runner. * Address review feedback - streetViewMarkerFactory now decides on the renderer alone. OR-ing the renderer with the namespace was wrong in the reverse direction: swapping *away* from Mapbox flips `primaryRenderer` to "maplibre" while the engine ref still holds the outgoing MapboxEngine, so the OR committed a control being rebuilt for MapLibre to a Mapbox marker — which by `onAdd` has no namespace left and throws. The renderer is right in both directions; the namespace stays a fallback only for a host that reports no renderer (Claude review). - Moved the stranded "Stand in for Google's metadata and embed endpoints" doc back above `mockStreetViewProvider`, where it belongs. My helper insertion had left it above an unrelated const with its own doc (Claude review, twice). - The e2e no longer forgives Mapbox failures by token state. It ignores only the bookkeeping endpoints — telemetry and `map-sessions`, which serve no map content and reject even for a valid token — and does so unconditionally. The project points at a third-party style, so no Mapbox content is fetched and a content failure would be a genuine surprise worth reporting (CodeRabbit). | 18 天前 | |
feat(plugins): dock Layer Swipe in the side panel and follow the dark theme (#2845) Layer Swipe's settings floated over the map as a MapLibre control with its own toolbar button and position submenu. The settings panel now opens in GeoLibre's dockable side panel (sharing the Style sidebar), while the slider and clipped comparison map stay on the map. The control stays mounted on the map for the plugin's whole lifetime; the dock only adopts its panel element while it shows it. Another docked panel displacing this one runs its render cleanup, and that must not stop the swipe. A MutationObserver catches the panel when the control builds it late (after fetching a basemap style), so basemap rebuilds and project restores land in the dock too. Closing the dock deactivates the plugin; the position submenu is gone. Bumps maplibre-gl-swipe to 0.14.0, which builds the comparison pane while tiles are still loading (the dock resizes the map on activation, which made the Mapbox swipe E2E flaky) and adds a dark color scheme with a `theme` option. The control follows the in-app theme through setTheme, and its --swipe-* variables map onto the design tokens. | 1 天前 | |
feat(mapbox): host Zarr layers on the Mapbox renderer (#2414) * feat(mapbox): host Zarr layers on the Mapbox renderer @carbonplan/zarr-layer is a CustomLayerInterface implementation that targets Mapbox GL as well as MapLibre, and the Zarr control only uses the style API both engines share, so the panel needed no adapter — only the gates that assumed a MapLibre map: - isMapboxPluginLayer recognises zarr-url store layers (no engine error, no "No Mapbox" badge) - supportsAddDataRenderer no longer withholds zarr on Mapbox - the untiled NetCDF/Kerchunk Zarr path locks Mercator on either 2D engine - STAC Zarr assets are addable on Mapbox; the NetCDF dialog drops its Mapbox-only image/unsupported-reference branches Adds e2e/mapbox-zarr.spec.ts (routed mini store, runs without a token) and updates the unit tests and docs. * Address Claude review feedback - NetCDF dialog: a remote NetCDF/HDF cube with a time axis now renders its selected slice as an image on both engines. The remote reader has no layer-refs builder, so the non-image branch had nothing for it and submit silently added no layer (pre-existing on MapLibre; reachable on Mapbox once the Mapbox-only image escape hatch was removed). - STAC: inline `canAddAsset` at its four call sites and drop the `canAddAssetToMap` wrapper, which no longer carried any renderer logic. * Address CodeRabbit review feedback - docs: state that the NetCDF/HDF and Kerchunk cube path locks the map to Mercator when a layer is added (the untiled renderer draws in Web Mercator), while Zarr Layer and STAC adds keep the current projection. * Address review feedback and update the Mapbox menu specs for Zarr - e2e: mapbox-deck-overlays.spec.ts and mapbox-add-data.spec.ts expected "Zarr Layer" to stay disabled on Mapbox; it is enabled now, so the entry moves to the enabled list (the CI E2E failure on this branch). - docs: the Mercator statement is limited to local NetCDF/HDF cubes and Kerchunk references; a remote NetCDF/HDF URL renders an image overlay (CodeRabbit). - docs: the Zarr validation row says pixel output was not confirmed here, so the gap noted in the PR survives the merge (Claude review). | 18 天前 | |
fix(web): Open Recent for saved projects (#2924) * fix: reopen web recent projects with file picker * style: auto-format (ruff + oxfmt) [pre-commit.ci] --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Qiusheng Wu <giswqs@gmail.com> | 22 分钟前 | |
feat(style-import): paste a style from the layer menu and the Style panel (#2385) * refactor(style-import): read a style in one place, in @geolibre/map rather than the app * feat(style-import): paste a style from the layer menu and the Style panel * fix(style-import): fade the Style panel note, and give the paste item its own icon * fix(style-import): use the same paste icon in the Style panel as in the layer menu * fix(qml-import): read a bare renderer-v2 root, so a pasted QML fragment imports * fix(style-import): stop the parsers calling a pasted style a file * docs: match the reworded style-import label, and describe the paste box * fix(style-import): route a bare labeling fragment to the QML parser | 20 天前 | |
test(e2e): move four feature specs and registry deep-link flows to nightly (#2918) * test(e2e): move four feature specs and the registry deep-link flows to nightly The core Playwright gate grew to ~11 min wall-clock (10.6-15.8 min over the last three main runs, ~25 min of test time), with identify-restore retrying on most runs. Move set-view, paste-style, style-manager and identify-restore to the nightly features project: each covers one dialog or interaction rather than something every user hits. Split the two registry install tests out of plugin-deep-link.spec.ts into plugin-deep-link-registry.spec.ts (nightly), so the docs-vs-link- name check that #2858 put in core stays per-commit. Core drops from 44 to 30 tests; the full suite still runs all 150. * test(e2e): accept either Whitebox search label in the a11y sweep The toolbox search box reads "Search tools or describe a task" only when an AI endpoint is configured (useWhiteboxSemanticSearch). CI builds have none, so the Processing toolbox sweep failed there while passing on machines with a key. Match the shared "Search tools" prefix. * test(e2e): open Plugins -> Installed in plugin-install.spec #2851 moved external plugins into an Installed submenu, so every plugin-install case that looked for the plugin at the top of the Plugins menu failed (the nightly suite has been red since). Add an openPluginsMenu helper that opens the submenu, waiting for it where a plugin is expected because external plugins load asynchronously. Also wait for the unsaved-changes prompt or the New project dialog instead of probing once for the prompt: it opens a beat after the menu closes, so the probe missed it and the test waited behind it. | 1 小时前 | |
test(e2e): move four feature specs and registry deep-link flows to nightly (#2918) * test(e2e): move four feature specs and the registry deep-link flows to nightly The core Playwright gate grew to ~11 min wall-clock (10.6-15.8 min over the last three main runs, ~25 min of test time), with identify-restore retrying on most runs. Move set-view, paste-style, style-manager and identify-restore to the nightly features project: each covers one dialog or interaction rather than something every user hits. Split the two registry install tests out of plugin-deep-link.spec.ts into plugin-deep-link-registry.spec.ts (nightly), so the docs-vs-link- name check that #2858 put in core stays per-commit. Core drops from 44 to 30 tests; the full suite still runs all 150. * test(e2e): accept either Whitebox search label in the a11y sweep The toolbox search box reads "Search tools or describe a task" only when an AI endpoint is configured (useWhiteboxSemanticSearch). CI builds have none, so the Processing toolbox sweep failed there while passing on machines with a key. Match the shared "Search tools" prefix. * test(e2e): open Plugins -> Installed in plugin-install.spec #2851 moved external plugins into an Installed submenu, so every plugin-install case that looked for the plugin at the top of the Plugins menu failed (the nightly suite has been red since). Add an openPluginsMenu helper that opens the submenu, waiting for it where a plugin is expected because external plugins load asynchronously. Also wait for the unsaved-changes prompt or the New project dialog instead of probing once for the prompt: it opens a beat after the menu closes, so the probe missed it and the test waited behind it. | 1 小时前 | |
test(e2e): move four feature specs and registry deep-link flows to nightly (#2918) * test(e2e): move four feature specs and the registry deep-link flows to nightly The core Playwright gate grew to ~11 min wall-clock (10.6-15.8 min over the last three main runs, ~25 min of test time), with identify-restore retrying on most runs. Move set-view, paste-style, style-manager and identify-restore to the nightly features project: each covers one dialog or interaction rather than something every user hits. Split the two registry install tests out of plugin-deep-link.spec.ts into plugin-deep-link-registry.spec.ts (nightly), so the docs-vs-link- name check that #2858 put in core stays per-commit. Core drops from 44 to 30 tests; the full suite still runs all 150. * test(e2e): accept either Whitebox search label in the a11y sweep The toolbox search box reads "Search tools or describe a task" only when an AI endpoint is configured (useWhiteboxSemanticSearch). CI builds have none, so the Processing toolbox sweep failed there while passing on machines with a key. Match the shared "Search tools" prefix. * test(e2e): open Plugins -> Installed in plugin-install.spec #2851 moved external plugins into an Installed submenu, so every plugin-install case that looked for the plugin at the top of the Plugins menu failed (the nightly suite has been red since). Add an openPluginsMenu helper that opens the submenu, waiting for it where a plugin is expected because external plugins load asynchronously. Also wait for the unsaved-changes prompt or the New project dialog instead of probing once for the prompt: it opens a beat after the menu closes, so the probe missed it and the test waited behind it. | 1 小时前 | |
feat: full-detail COPC areas for point cloud annotation (#2769) * feat: full-detail COPC areas for point cloud annotation "Full detail in view" loads every point of a streamed COPC inside the view at the file's full resolution and pins it (maplibre-gl-lidar 0.20 loadRegion), so a session labels every point rather than the level of detail the zoom happened to load. "Release full detail" unpins it. * Address review feedback - Update the session hint after a full-detail load only when the session is still on the cloud that loaded. - Track pinned clouds per cloud, so each can be released on its own; Release applies to the session's cloud, else the selected one. - Keep Full detail disabled while a load runs, whatever re-renders. * Address review feedback - Keep pinned full-detail clouds per LiDAR control, outside the panel, so a reopened panel still offers to release them. - Enable Full detail in view only for a streamed COPC (source with .copc. and octree node ranges), not a whole-file LAS/LAZ or an EPT. - Note in docs/maintenance.md what the annotator assumes about loadRegion/clearPinnedRegion and how it detects a streamed COPC. * Address Claude review feedback - Hide Release full detail while a full-detail load runs, so a release cannot be undone by the load re-pinning when it finishes. - Disable Full detail in view until the map is available. * Address Claude review feedback - Hide Release full detail as soon as a full-detail load starts, rather than on the next re-render, so a release cannot be clicked mid-load. | 4 天前 | |
feat(print): export editable SVG print layouts (#2351) Keep layout text, legends, colorbars, and other drawn elements editable in vector editors while embedding the captured map image. Reuse the preview renderer and verify SVG, PNG, and PDF exports in both themes. | 24 天前 | |
ci(fmt): add ruff + oxfmt auto-format workflow and config (#1327) * ci(fmt): add ruff + oxfmt auto-format workflow and config Adds a GitHub Actions workflow that runs ruff format/check on Python and notebooks and oxfmt on JS/TS/JSON/CSS/YAML/TOML, then pushes a style commit back to the branch. Mirrors the local pre-commit hooks so CI and local runs produce identical output. - .github/workflows/format.yml: pinned ruff==0.15.22, oxfmt@0.59.0 - ruff.toml: line-length 100, rules F/I/W/E, py310 target - .oxfmtrc.json: width 100, double quotes, lockfile/generated excludes - .pre-commit-config.yaml: local ruff + oxfmt hooks (offline-friendly) - .gitattributes: normalize all text to LF, explicit binary rules - docs/contributing.md: document the new formatting pipeline * ci(fmt): switch auto-format from GitHub Actions to pre-commit.ci pre-commit.ci authenticates via its own GitHub App, so the auto-format bot can push fixes back on fork PRs (where GITHUB_TOKEN is read-only). It only runs on PRs, so auto-formatted commits never bypass review. - Delete .github/workflows/format.yml - Add autofix/autoupdate config to .pre-commit-config.yaml; the ruff and oxfmt version pins there are now the single source of truth - Trim ignorePatterns comments in .oxfmtrc.json - Rewrite the coding-conventions section in docs/contributing.md * ci(fmt): address review feedback on ruff.toml and .gitattributes - .gitattributes: add explicit binary rules for *.icns, *.pbf, *.pmtiles (icon.icns, MapLibre glyph pbfs, osm.pbf fixtures, and mini.pmtiles are all tracked in-repo; closes the explicit-safety-net gap flagged in review) - ruff.toml: drop the misleading lines-after-imports = -1 (no-op default) and its comment; add .ruff_cache to extend-exclude so the intent survives any future change to ruff's default exclusion list - .gitignore: ignore .ruff_cache/ - .pre-commit-config.yaml: drop the redundant `files: \.(py|ipynb)$` regex (types_or already scopes the ruff hooks to python/pyi/jupyter) and fix the nbstripout hook indentation * ci(fmt): exclude generated whitebox-menu-catalog from oxfmt The file is auto-generated by scripts/gen-whitebox-menu-catalog.mjs (its header says 'do not hand-edit'), so add it to .oxfmtrc.json's ignorePatterns alongside the other generated catalogs. * ci(fmt): run ruff check --fix before ruff format Lint autofixes (import sorting via I, unused-import removal via F) can change layout; the formatter must run last so a single pre-commit pass stabilizes the file. Matches the order recommended by ruff's docs and astral-sh/ruff-pre-commit. * Update .gitattributes Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> * style: auto-format (ruff + oxfmt) [pre-commit.ci] * Address Claude review feedback - docs/contributing.md: note that oxfmt also sorts package.json keys into its conventional order, so contributors expect key reordering (not just whitespace changes) in future package.json diffs. * Address Claude review feedback - .oxfmtrc.json: drop the "python/**" ignore. It was a no-op for .py/.ipynb (the hook's types_or never passes them, and *.ipynb is excluded globally) while wrongly hiding real JS/TOML under python/ from oxfmt; the generated python/src/geolibre/static stays excluded via "**/static/**". Format the newly covered python/pyproject.toml and python/src/geolibre/_frontend.js. - ruff.toml: delete the [lint.pycodestyle] max-line-length block; it equaled the top-level line-length, which is already the default, so behavior is unchanged. Reworded the E-rules comment that pointed at it. - docs/contributing.md: restore the 2-space list-continuation indent on the mixed-line-ending bullet so the inline code span isn't split oddly. --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Qiusheng Wu <giswqs@gmail.com> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> | 2 个月前 | |
fix(a11y): un-nest layer rows and broaden the axe sweep (#2895) * fix(a11y): un-nest layer rows and broaden the axe sweep The layer panel rendered each row as a role="button" card wrapping its own controls, a serious axe nested-interactive finding the a11y specs allowlisted. Rows, group headers and the Background card are now listitems in a labelled list, carrying their depth as aria-level, and selection moves to a dedicated name button with aria-pressed. Both allowlists (a11y.spec.ts, rtl.spec.ts) are gone. A new nightly a11y-screens.spec.ts scans the Add Data panels and dialogs, the Processing toolbox, the Style panel for a vector and a raster layer, every Settings section, New Project with Examples, Project History, Print Layout, Manage Plugins, and an error toast. It found and this fixes: unnamed selects in Geocoding settings and the Whitebox toolbox, and the raster panel's faint text at 3.5:1. keyboard-only.spec.ts (core) adds an OGC API - Features layer by URL, changes its circle radius, and exports it as GeoJSON with no pointer input. The shared axe helper waits for animations so a mid-fade frame is not reported as a contrast failure. Part of #2858 * Address review feedback - LayerRow: F2 on a layer's name button starts a rename, the keyboard counterpart of the double-click (CodeRabbit), with a component test. - a11y-screens.spec.ts: slugify the Add Data attachment labels. - a11y.ts: bound the animation wait at 10 s so a stuck animation fails with a clear error. - keyboard-only.spec.ts: tabTo treats a not-yet-rendered target as not focused instead of stalling on the default action timeout. - layer-panel.spec.ts: update a comment that still described the card as a role="button" wrapper (Claude review). * Address Claude review feedback - Drop the data-layer-card markers on the layer and Background cards: their only consumer was the nested-interactive allowlist this PR removes. | 6 小时前 | |
Add local 3D model import and georeferenced Shanghai sample (#2258) * feat: add local 3D model import and georeferenced Shanghai sample * style: auto-format (ruff + oxfmt) [pre-commit.ci] * fix: preserve scale edits during local model loading * Address Claude review feedback - Use the logical `me-2` margin on the "Choose local model" button icon so it mirrors correctly in RTL locales, matching the rest of the dialog. - Keep `DEFAULT_DECK_VIZ_SCENEGRAPH.sizeMinPixels` at 1 so projects saved before the field was written keep their pixel floor, and persist an explicit `sizeMinPixels: 0` from the Add Data dialog so new (meter-scale) placements get the intended geographic sizing. - Drop the unread `sizeMinPixels`/`orientationRoll`/`translation` fields from the Shanghai sample and note which fields the dialog actually prefills. - Cap local model embedding at 24 MB (matching the KML `<Model>` inline limit) with a dedicated error message, instead of reading an arbitrarily large file into memory and into the saved project. - Reject external `uri` values anywhere in the asset (texture/vendor extensions included, `extras` excluded), not just top-level buffers/images. * Address CodeRabbit review feedback - Retire a pending local-model read when the dialog moves on: the layer-type select stays enabled while a file is being read, so a slow read could apply its model URL, filename, or error to a form the user had already switched away from. A token bumped on each read and on a layer-kind change gates the completion path. * Address CodeRabbit review feedback - Clear `isLoadingModel` when a layer-kind change retires a pending model read, so the new form is not left unsubmittable (`submitDisabled` includes the flag) until an abandoned read settles, and only clear it in `finally` when the read is still the current one, so a retired read cannot unblock the form while its replacement is still running. * Address Claude review feedback - Reset the placement when the sample select goes back to "Custom model": clearing only the URL left the previous sample's coordinates, scale, bearing and altitude describing a model the user is no longer using. Scale returns to the neutral default rather than the example's magnification. - Walk the glTF JSON iteratively when checking for external resources. The recursive walk overflowed the stack at a nesting depth `JSON.parse` still accepts, which surfaced a readable file as a generic "invalid model" error. * Fix the deep-nesting glTF test on Node 22 The fixture was built with JSON.stringify, which recurses: at 5,000 levels it overflowed the stack on the CI runner (Node 22) before the code under test ran, so the assertion saw a RangeError instead of the expected rejection. Build the asset as text instead, and pick the deepest nesting the running JSON.parse accepts so the test exercises the walk rather than the host's parser limits. --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> | 28 天前 | |
ci(fmt): add ruff + oxfmt auto-format workflow and config (#1327) * ci(fmt): add ruff + oxfmt auto-format workflow and config Adds a GitHub Actions workflow that runs ruff format/check on Python and notebooks and oxfmt on JS/TS/JSON/CSS/YAML/TOML, then pushes a style commit back to the branch. Mirrors the local pre-commit hooks so CI and local runs produce identical output. - .github/workflows/format.yml: pinned ruff==0.15.22, oxfmt@0.59.0 - ruff.toml: line-length 100, rules F/I/W/E, py310 target - .oxfmtrc.json: width 100, double quotes, lockfile/generated excludes - .pre-commit-config.yaml: local ruff + oxfmt hooks (offline-friendly) - .gitattributes: normalize all text to LF, explicit binary rules - docs/contributing.md: document the new formatting pipeline * ci(fmt): switch auto-format from GitHub Actions to pre-commit.ci pre-commit.ci authenticates via its own GitHub App, so the auto-format bot can push fixes back on fork PRs (where GITHUB_TOKEN is read-only). It only runs on PRs, so auto-formatted commits never bypass review. - Delete .github/workflows/format.yml - Add autofix/autoupdate config to .pre-commit-config.yaml; the ruff and oxfmt version pins there are now the single source of truth - Trim ignorePatterns comments in .oxfmtrc.json - Rewrite the coding-conventions section in docs/contributing.md * ci(fmt): address review feedback on ruff.toml and .gitattributes - .gitattributes: add explicit binary rules for *.icns, *.pbf, *.pmtiles (icon.icns, MapLibre glyph pbfs, osm.pbf fixtures, and mini.pmtiles are all tracked in-repo; closes the explicit-safety-net gap flagged in review) - ruff.toml: drop the misleading lines-after-imports = -1 (no-op default) and its comment; add .ruff_cache to extend-exclude so the intent survives any future change to ruff's default exclusion list - .gitignore: ignore .ruff_cache/ - .pre-commit-config.yaml: drop the redundant `files: \.(py|ipynb)$` regex (types_or already scopes the ruff hooks to python/pyi/jupyter) and fix the nbstripout hook indentation * ci(fmt): exclude generated whitebox-menu-catalog from oxfmt The file is auto-generated by scripts/gen-whitebox-menu-catalog.mjs (its header says 'do not hand-edit'), so add it to .oxfmtrc.json's ignorePatterns alongside the other generated catalogs. * ci(fmt): run ruff check --fix before ruff format Lint autofixes (import sorting via I, unused-import removal via F) can change layout; the formatter must run last so a single pre-commit pass stabilizes the file. Matches the order recommended by ruff's docs and astral-sh/ruff-pre-commit. * Update .gitattributes Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> * style: auto-format (ruff + oxfmt) [pre-commit.ci] * Address Claude review feedback - docs/contributing.md: note that oxfmt also sorts package.json keys into its conventional order, so contributors expect key reordering (not just whitespace changes) in future package.json diffs. * Address Claude review feedback - .oxfmtrc.json: drop the "python/**" ignore. It was a no-op for .py/.ipynb (the hook's types_or never passes them, and *.ipynb is excluded globally) while wrongly hiding real JS/TOML under python/ from oxfmt; the generated python/src/geolibre/static stays excluded via "**/static/**". Format the newly covered python/pyproject.toml and python/src/geolibre/_frontend.js. - ruff.toml: delete the [lint.pycodestyle] max-line-length block; it equaled the top-level line-length, which is already the default, so behavior is unchanged. Reworded the E-rules comment that pointed at it. - docs/contributing.md: restore the 2-space list-continuation indent on the mixed-line-ending bullet so the inline code span isn't split oddly. --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Qiusheng Wu <giswqs@gmail.com> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> | 2 个月前 | |
ci(preview): smoke-test each deployed PR preview for a clean console (#2922) * ci(preview): smoke-test each deployed PR preview for a clean console Previews deploy to Cloudflare under /demo/, but nothing loaded the deployed app, and no spec failed on console errors. That is how #2916 (deployment.json 404) and #2919 (base-path fetches from the site root) reached web.geolibre.app and every preview unnoticed. - e2e/preview/: a config and spec that load PREVIEW_URL, wait for the MapLibre map to report loaded(), and fail on any console.error, page error, or same-origin 4xx/5xx. - pr-preview-deploy.yml: a smoke job runs it against the Cloudflare deployment from a default-branch checkout with a read-only token, and a smoke-status job posts the result as a commit status on the PR head, since workflow_run jobs do not appear in a PR's checks. - smoke.spec.ts (core): the same clean-console check against the local build on every PR (~3 s). - collectPageProblems and waitForMapLoaded shared in e2e/helpers.ts. * Address review feedback - collectPageProblems: always count the page's own document response. It can arrive before the frame commits its URL, so the same-origin check against page.url() dropped a 404'd app (Claude review). - preview config: collapse repeated slashes in PREVIEW_URL, so a deployment URL with a trailing slash plus "/demo/" still loads the app's directory (Claude review, CodeRabbit). - preview spec: attach and log the collected problems even when an earlier assertion fails first, so a 404'd app reports its status. - pr-preview-deploy.yml: document why smoke-status needs statuses: write (CodeRabbit / zizmor). | 52 分钟前 | |
fix(stac): sign Planetary Computer assets (#2247) * fix(stac): sign Planetary Computer assets * fix(stac): validate restored asset identity * fix(stac): keep signed URLs out of projects * Address review feedback - Serialize remote vector adds: addVectorLayersFromUrl read the layer ids it created as a before/after diff of control.getLayers(), so two overlapping adds shared a "before" snapshot and each claimed the other's layers, letting the STAC panel stamp one asset's access record onto a layer loaded for a different asset. The diff now runs one at a time, in a new addVectorLayersThroughControl helper with an overlap regression test. - Share one signing request per asset: planetaryComputerSignedHref only deduped completed signs, so restoring a project with several layers on one asset issued a request for each. In-flight requests are now shared, and the signed-URL cache drops expired then oldest entries at 200 rather than growing for the life of the session. - Give the STAC download button feedback while an asset is signed: it disables for the round trip and reports a failure in the panel status instead of dropping it. Claude-Session: https://claude.ai/code/session_01UVcrck818W9UueSqmUbvsL * Address CodeRabbit review feedback - Propagate cancellation into asset signing: the STAC panel already passes its AbortController to visualizeAsset, but the signing request ignored it, so a stalled signer left an add (or the new Download round trip) pending after the panel closed. readableStacAssetHref/readableStacLayerHref now take an optional signal and reject with AbortError on abort rather than falling back to the unsigned URL a cancelled add cannot read. The request itself keeps running because it is shared with any other caller waiting on the same asset. Claude-Session: https://claude.ai/code/session_01UVcrck818W9UueSqmUbvsL * Address Claude review feedback - Scope a remote vector add to its own url instead of serializing every add app-wide: the module-level queue fixed the overlapping-add race but made one slow download block every other remote vector add anywhere in the app. The before/after diff now also requires the layer's recorded source url to be the one this call passed, which the control echoes back verbatim, so overlapping loads keep their own layers without waiting on each other. - Key the STAC Download button's in-flight flag to the asset being signed, so switching the asset dropdown mid-sign no longer finds the other asset's button disabled. Claude-Session: https://claude.ai/code/session_01UVcrck818W9UueSqmUbvsL * Address review feedback - Bound a shared signing request: the pending entry that lets callers share one request is cleared when it settles, so a signer that hangs would have left the asset unsignable for the rest of the session. The request now carries its own 20s timeout, independent of any caller's cancellation. A test covers a failed request being retried rather than sticking. - Say so when a STAC asset-access record cannot be stored because the layer is not in the store yet, and document the ordering it depends on, so a future change to an add path fails loudly instead of quietly dropping signed-asset identity from saved projects. - Restore the note that the PMTiles branch reads unsigned deliberately. Claude-Session: https://claude.ai/code/session_01UVcrck818W9UueSqmUbvsL | 30 天前 | |
feat(stac): browse a static catalog as a tree and search from what you pick (#1945) * feat(stac): browse a static catalog as a tree and search from what you pick * test(stac): cover the panel wiring, the extent lookup and the entries a search starts from * style: auto-format (ruff + oxfmt) [pre-commit.ci] * fix(stac): refuse a bounding box with no middle, and flatten every one in one place * fix(stac): name the group a tree row opens and how deep the row sits * test(stac): pin the extent a search flies to, modifier clicks, and folders read only when opened * fix(stac): drop a collection extent that lands after the user has asked for another * fix(stac): keep the arrows working while a modifier is held * fix(stac): let a collection that holds collections be closed and opened again * fix(stac): search a catalog that carries its own items, and name a file that has no folder * fix(stac): open a folder with the arrows without changing what is chosen * fix(stac): search a catalog that holds sub-catalogs and items of its own * fix(stac): read a collection when it is chosen, and let a node with nothing inside be chosen too * fix(stac): drop a request for items the user has since asked to leave * fix(stac): collapse a chosen collection without letting go of it * fix(stac): only take a search generation when a search actually runs * fix(stac): keep a stale search from reporting its failure over the current one * refactor(stac): cancel the reading a catalog switch makes stale instead of ignoring it * perf(stac): stop a search's walk when another search replaces it --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> | 1 个月前 | |
feat(stac): add GeoParquet assets to map (#1976) * feat(stac): add GeoParquet assets to map * fix(stac): read Planetary Computer GeoParquet assets Adding a GeoParquet asset from Planetary Computer failed with "IO Error: No files found that match the pattern abfs://us-census/...". Two separate causes, both on the path this branch just opened up: - browserAssetHref only rewrote s3:// hrefs, so an abfs:// href reached DuckDB verbatim and could not be resolved. Azure hrefs name the container first and carry the account out of band in the table extension's storage options, so the account is threaded in from the asset (falling back to the item) rather than read out of the URL. abfss:// and az:// share the convention and are handled with it. - The resolved URL still answers 409 on private containers such as ai4edataeuwest. Assets on Azure blob storage are now signed at add time with the SAS manager already shipped by maplibre-gl-planetary-computer, imported lazily so the control is only pulled in when such an asset is actually added. Tokens are per collection and expire within the hour, so they are minted on add rather than on parse, and the upstream manager caches them. Public containers such as NAIP are unaffected: anything that is not an Azure blob, or that cannot be signed, is read unsigned as before. Verified against the live service: the reported href resolves, returns 409 unsigned, and returns 206 with PAR1 magic once signed, so DuckDB's range reads work. GeoJSON provenance keeps the unsigned href so no expiring token is written into a saved project. * test(stac): follow GeoParquet becoming addable Both STAC E2E specs asserted the behaviour this branch deliberately changed, and have been failing since f91d035: - stac-api-panel expected "Dataset root — Parquet (not addable)". The asset is addable now, so the suffix is gone. - stac-pmtiles clicked Add on whatever the panel preselected, which was the archive only because the parquet listed ahead of it could not be added. The panel preselects the first addable asset, so that default is now the parquet. The spec is about an archive reaching the map over range reads, so it chooses the archive explicitly instead of relying on which asset happens to come first. * Address Claude review feedback - Sign only the GeoParquet path. PMTiles and COG keep their href as the layer's source (addPMTilesAsset stores it verbatim via createPMTilesStoreLayer, and addCogLayer takes the url as the persisted source), and the store is what a saved project is written from, so signing them wrote an expiring SAS token into .geolibre.json — a credential in a shared project file, and a layer that silently stops loading once the token lapses. Those formats read unsigned again, exactly as they did before GeoParquet became addable, so nothing regresses; signing them properly wants a per-request token rather than one baked in at add time. - The geojson branch no longer needs its unsigned-provenance note now that nothing upstream of it signs. * Address Claude review feedback - An Azure href with no account to resolve it against stayed abfs:// and was still offered as addable, so Add was enabled on an asset none of the readers can open — a regression for catalogs that omit table:storage_options, since parquet used to be labelled not-addable outright. assetFormat now answers null for an href that is not http(s), which restores its distinction from assetDisplayFormat: the asset is still named by its format, just not offered. - Corrected the readableHref docstring. It claimed only PMTiles and COG keep a URL as their layer source; the vector path does too, through createVectorStoreLayer, which records the URL as both source.url and sourcePath, and restoreVectorLayers replays it without re-signing. The comment now states the limitation instead of denying it: a saved project holding a signed GeoParquet layer stops reloading it once the token lapses. Narrowing signing to GeoParquet still stands on its own ground — it is the format this branch made addable, and the only one that cannot be read at all unsigned. - Covered the item-level table:storage_options fallback, and the named-but-not-offered case above. * Address CodeRabbit review feedback - Read the canonical ABFS form. Azure documents abfs[s]://<container>@<account>.dfs.core.windows.net/<path>, which names both parts itself; browserAssetHref was reading the hostname as the container, so such an href either stayed unresolved or produced a URL with the DFS host sitting in the path where the container belongs. The container now comes from the userinfo and the account from the DFS hostname, with the container-first shorthand unchanged and still leaning on table:storage_options. Regression tests cover the canonical form, that it wins over an account named beside it, and that the canonical host without a container stays unresolved. | 1 个月前 | |
feat(stac): add Zarr assets to the map (#2005) * feat(stac): add Zarr assets to the map with a variable picker * test(stac): cover Zarr assets against captured catalog responses * fix(stac): carry a Zarr store's CRS and refuse one that cannot be opened * style: auto-format (ruff + oxfmt) [pre-commit.ci] * fix(stac): keep a Zarr href's query out of the store path and the asset label honest * fix(stac): add a Zarr variable only when the store says it is an array * style: auto-format (ruff + oxfmt) [pre-commit.ci] * fix(stac): treat only metadata that names an array as drawable * style: auto-format (ruff + oxfmt) [pre-commit.ci] * refactor(stac): name the reason Add is disabled instead of nesting ternaries * fix(stac): recognize a Zarr href that names an array inside the store * fix(stac): say which problem stopped a Zarr variable from being added * fix(stac): recognize a Zarr v2 group by its own metadata file * fix(stac): name an address that cannot be read key by key as its own problem * fix(stac): translate the variable picker's tooltip * fix(stac): keep an undrawable keyed asset from offering the item's other variables * fix(stac): restore the translated not-addable label * fix(stac): keep probing when a store answers with something that is not metadata * fix(stac): keep asking after a refusal, and declare the storage options instead of casting * test(stac): pin the preflight to the array an embedded href names * test(stac): assert the Zarr layer reaches the Layers panel, not either match * fix(stac): believe a store's metadata only when it parses * fix(stac): draw only variables spanning the horizontal axes * fix(stac): read an axis whatever its case, and ignore one the spec never defines * refactor(stac): reuse the storage-option types and drop guards the renderer makes dead * fix(stac): recognize an Icechunk repository declared on the item * fix(stac): keep probing when one key's request is refused outright * fix(stac): refuse a keyless-unfriendly Zarr address before the click --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> | 1 个月前 | |
fix: support story maps on Mapbox (#2478) * fix: support story maps on Mapbox * Address Claude review feedback - Update story markers when their render container resizes. - Initialize compose controls from camera motion instead of tile loading. - Track Cesium camera motion on rendered frames. * Address CodeRabbit review feedback - Rebind story map effects when the renderer engine changes. - Hide renderer-neutral markers while coordinates cannot be projected. - Wait for camera settlement before capturing story handout frames. * Address Claude review feedback - Preserve the default MapLibre pin anchor and vertical offset. * Address review feedback - Expose camera application completion for asynchronous renderers. - Await ArcGIS camera placement before handout capture. - Add Story Map camera settling tests. * Address review feedback - Skip camera application when handout export is already cancelled. - Replace the Mapbox test sleep with renderer load detection. - Tighten the story marker assertion. * Address Claude review feedback - Inject the Mapbox token into browser settings for the authenticated test. - Require renderer readiness to remain stable before handout capture. - Cover Cesium camera settling in the Story Map unit tests. * Address Claude review feedback - Resolve Cesium camera placement after the first rendered scene frame. - Use the supported screenshot-readiness DOM contract in the Mapbox test. - Remove the React fiber inspection from the E2E helper. * Address Claude review feedback - Name the hand-mirrored MapLibre default-pin offset (DEFAULT_MARKER_OFFSET_Y) in the story marker and list it under maplibre-gl in docs/maintenance.md so a bump that changes the pin geometry is checked. * Fix cesium-engine tests for the promise-returning applyView - Give the fake Cesium scene a postRender event (whose addEventListener returns a remover, like Cesium's) and requestRender, so applyView's next-frame wait no longer throws an unhandled rejection after each test. | 15 天前 | |
Add ArcGIS basemap picker and native layer control (#2444) * Add ArcGIS basemap picker and native layer control * Address review feedback - Reuse the existing ArcGIS translation key for the basemap section. - Read the basemap preferences action through component selectors. - Synchronize native visibility callbacks to prevent store updates losing toggles; add a regression test. - Clarify the shared primary-pane Layer Control default; keep the widget consistent with the active-by-default plugin. * Address review feedback - Read current preferences when selecting an ArcGIS basemap to preserve concurrent changes. - Document all ten supported Esri basemap choices. - Allow the two-boot Style Manager E2E flow sufficient total runtime on CI without changing assertion timeouts. | 17 天前 | |
ci(fmt): add ruff + oxfmt auto-format workflow and config (#1327) * ci(fmt): add ruff + oxfmt auto-format workflow and config Adds a GitHub Actions workflow that runs ruff format/check on Python and notebooks and oxfmt on JS/TS/JSON/CSS/YAML/TOML, then pushes a style commit back to the branch. Mirrors the local pre-commit hooks so CI and local runs produce identical output. - .github/workflows/format.yml: pinned ruff==0.15.22, oxfmt@0.59.0 - ruff.toml: line-length 100, rules F/I/W/E, py310 target - .oxfmtrc.json: width 100, double quotes, lockfile/generated excludes - .pre-commit-config.yaml: local ruff + oxfmt hooks (offline-friendly) - .gitattributes: normalize all text to LF, explicit binary rules - docs/contributing.md: document the new formatting pipeline * ci(fmt): switch auto-format from GitHub Actions to pre-commit.ci pre-commit.ci authenticates via its own GitHub App, so the auto-format bot can push fixes back on fork PRs (where GITHUB_TOKEN is read-only). It only runs on PRs, so auto-formatted commits never bypass review. - Delete .github/workflows/format.yml - Add autofix/autoupdate config to .pre-commit-config.yaml; the ruff and oxfmt version pins there are now the single source of truth - Trim ignorePatterns comments in .oxfmtrc.json - Rewrite the coding-conventions section in docs/contributing.md * ci(fmt): address review feedback on ruff.toml and .gitattributes - .gitattributes: add explicit binary rules for *.icns, *.pbf, *.pmtiles (icon.icns, MapLibre glyph pbfs, osm.pbf fixtures, and mini.pmtiles are all tracked in-repo; closes the explicit-safety-net gap flagged in review) - ruff.toml: drop the misleading lines-after-imports = -1 (no-op default) and its comment; add .ruff_cache to extend-exclude so the intent survives any future change to ruff's default exclusion list - .gitignore: ignore .ruff_cache/ - .pre-commit-config.yaml: drop the redundant `files: \.(py|ipynb)$` regex (types_or already scopes the ruff hooks to python/pyi/jupyter) and fix the nbstripout hook indentation * ci(fmt): exclude generated whitebox-menu-catalog from oxfmt The file is auto-generated by scripts/gen-whitebox-menu-catalog.mjs (its header says 'do not hand-edit'), so add it to .oxfmtrc.json's ignorePatterns alongside the other generated catalogs. * ci(fmt): run ruff check --fix before ruff format Lint autofixes (import sorting via I, unused-import removal via F) can change layout; the formatter must run last so a single pre-commit pass stabilizes the file. Matches the order recommended by ruff's docs and astral-sh/ruff-pre-commit. * Update .gitattributes Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> * style: auto-format (ruff + oxfmt) [pre-commit.ci] * Address Claude review feedback - docs/contributing.md: note that oxfmt also sorts package.json keys into its conventional order, so contributors expect key reordering (not just whitespace changes) in future package.json diffs. * Address Claude review feedback - .oxfmtrc.json: drop the "python/**" ignore. It was a no-op for .py/.ipynb (the hook's types_or never passes them, and *.ipynb is excluded globally) while wrongly hiding real JS/TOML under python/ from oxfmt; the generated python/src/geolibre/static stays excluded via "**/static/**". Format the newly covered python/pyproject.toml and python/src/geolibre/_frontend.js. - ruff.toml: delete the [lint.pycodestyle] max-line-length block; it equaled the top-level line-length, which is already the default, so behavior is unchanged. Reworded the E-rules comment that pointed at it. - docs/contributing.md: restore the 2-space list-continuation indent on the mixed-line-ending bullet so the inline code span isn't split oddly. --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Qiusheng Wu <giswqs@gmail.com> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> | 2 个月前 |
| 文件 | 最后提交记录 | 最后更新时间 |
|---|---|---|
| 2 天前 | ||
| 12 天前 | ||
| 52 分钟前 | ||
| 1 小时前 | ||
| 6 小时前 | ||
| 6 小时前 | ||
| 16 天前 | ||
| 10 天前 | ||
| 2 个月前 | ||
| 12 小时前 | ||
| 12 天前 | ||
| 26 天前 | ||
| 2 天前 | ||
| 1 个月前 | ||
| 12 小时前 | ||
| 4 小时前 | ||
| 2 个月前 | ||
| 21 天前 | ||
| 52 分钟前 | ||
| 12 天前 | ||
| 6 小时前 | ||
| 2 个月前 | ||
| 28 天前 | ||
| 6 小时前 | ||
| 12 天前 | ||
| 12 天前 | ||
| 12 天前 | ||
| 18 天前 | ||
| 19 天前 | ||
| 17 天前 | ||
| 15 天前 | ||
| 18 天前 | ||
| 17 天前 | ||
| 1 天前 | ||
| 1 天前 | ||
| 15 天前 | ||
| 18 天前 | ||
| 1 天前 | ||
| 18 天前 | ||
| 22 分钟前 | ||
| 20 天前 | ||
| 1 小时前 | ||
| 1 小时前 | ||
| 1 小时前 | ||
| 4 天前 | ||
| 24 天前 | ||
| 2 个月前 | ||
| 6 小时前 | ||
| 28 天前 | ||
| 2 个月前 | ||
| 52 分钟前 | ||
| 30 天前 | ||
| 1 个月前 | ||
| 1 个月前 | ||
| 1 个月前 | ||
| 15 天前 | ||
| 17 天前 | ||
| 2 个月前 |