Skip to content

feat(core,template): <MapPreview> — serverless any-size geo preview via MapLibre + PMTiles (po-6sr.1)#1644

Merged
anuveyatsu merged 2 commits into
mainfrom
polecat/furiosa/po-6sr.1@mrak0qx3
Jul 7, 2026
Merged

feat(core,template): <MapPreview> — serverless any-size geo preview via MapLibre + PMTiles (po-6sr.1)#1644
anuveyatsu merged 2 commits into
mainfrom
polecat/furiosa/po-6sr.1@mrak0qx3

Conversation

@anuveyatsu

@anuveyatsu anuveyatsu commented Jul 7, 2026

Copy link
Copy Markdown
Member

Phase 1 of the GIS epic po-6sr (§6): a serverless map preview that kills the "can't preview big GeoJSON" problem. Bead: po-6sr.1. Do not merge — mayor lands.

What

  • @portaljs/core: new <MapPreview> (maplibre-gl + pmtiles, exported from the public API). Renders any-size vector PMTiles from R2 / any static HTTP host via HTTP range requests — only the tiles in view are fetched, no tile server.
    • Auto-fits the tileset bounds from the PMTiles header (or takes center/zoom).
    • Sensible default styling per vector layer (fill/line/point trio filtered by geometry type).
    • Click-to-inspect: popup with the clicked feature's properties (tippecanoe preserves source attributes).
    • Keyless basemap (MapLibre demo tiles) with an offline plain-background fallback (basemap={false} for fully offline); basemap failure never takes the data layer down.
    • SSR-safe by construction: maplibre-gl/pmtiles load via dynamic import() inside useEffect, so builds (including CI "Build site") never touch window.
  • Catalog template: pmtiles added to DataFormat; local components/MapPreview.tsx wired into the showcase via next/dynamic ssr:false (the maplibre chunk loads only when a dataset actually has a PMTiles resource).
  • Live demo dataset: @reference/world-boundaries — Natural Earth 110m countries (public domain), tiled with tippecanoe -z5 to a 344 KB archive, committed inline under public/data/ per the sample-data fence, with a .gitattributes *.pmtiles -text guard.
  • Docs: manual tippecanoe → PMTiles ingest path in the template README ("Map tier" section) + <MapPreview> usage in the core README. Auto-ingest is a later phase (separate bead), per the epic.

Verification

Drove the built template in a real (headed) browser:

  • PMTiles fetched via 206 Partial Content range requests (header, then per-tile).
  • Map renders countries over the basemap; pan/zoom fetch only tiles in view.
  • Clicking a country pops feature properties (NAME / ISO_A3 / CONTINENT / POP_EST / GDP_MD).
  • WebGL-unavailable path degrades to a clear in-map error message.

Local CI parity: all three packages build, @portaljs/ckan lint clean, template next build green (demo page prerendered).

Out of scope (later phases)

Auto-ingest pipeline (container compute), GeoParquet + DuckDB spatial (<GeoQuery>), COG/raster, Koop/OGC adapters.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Added interactive MapPreview support for large tiled PMTiles datasets, including click-to-inspect feature popups.
    • Extended catalog resource support with a new pmtiles format and a world boundaries PMTiles sample dataset.
    • Updated example and core docs with PMTiles “map tier” guidance and preview usage.
  • Bug Fixes
    • Improved graceful degradation so map previews remain usable when the basemap network fails.
  • Chores
    • Added required map-rendering dependencies and updated inline-shipping rules for bundled data.

…ia MapLibre + PMTiles (po-6sr.1)

Phase 1 of the GIS epic (po-6sr §6): kill the "can't preview big GeoJSON"
problem with a serverless map preview over a single PMTiles archive.

- @portaljs/core: new <MapPreview> (maplibre-gl + pmtiles), exported from the
  public API. Renders any-size vector PMTiles from R2/any HTTP host via range
  requests — no tile server. Auto-fits the tileset bounds from the PMTiles
  header, sensible fill/line/point styling per vector layer, click-to-inspect
  feature properties, keyless demo-tiles basemap with offline fallback.
  SSR-safe: maplibre-gl/pmtiles load via dynamic import() in the browser only.
- catalog template: 'pmtiles' DataFormat + local MapPreview component wired
  into the showcase (next/dynamic ssr:false, chunk loads only when a dataset
  has a PMTiles resource); bundled @reference/world-boundaries demo (Natural
  Earth 110m countries, public domain, 344 KB, tippecanoe -z5) kept inline per
  the sample-data fence, with a .gitattributes -text guard for *.pmtiles.
- docs: manual tippecanoe→PMTiles ingest path (interim until auto-ingest ships
  as a later phase) in the template README + <MapPreview> usage in the core
  README.

Verified in a real browser: tiles fetched via 206 range requests, map renders,
click popup shows feature properties.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@changeset-bot

changeset-bot Bot commented Jul 7, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: dcf1558

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@vercel

vercel Bot commented Jul 7, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
www.portaljs.com Ready Ready Preview, Comment Jul 7, 2026 5:08pm
7 Skipped Deployments
Project Deployment Actions Updated (UTC)
portaljs-alan-turing Ignored Ignored Preview Jul 7, 2026 5:08pm
portaljs-ckan Ignored Ignored Preview Jul 7, 2026 5:08pm
portaljs-ckan-ssg Ignored Ignored Preview Jul 7, 2026 5:08pm
portaljs-fivethirtyeight Ignored Ignored Preview Jul 7, 2026 5:08pm
portaljs-git-example Ignored Ignored Preview Jul 7, 2026 5:08pm
portaljs-learn Ignored Ignored Preview Jul 7, 2026 5:08pm
portaljs-openspending Ignored Ignored Preview Jul 7, 2026 5:08pm

Request Review

@coderabbitai

coderabbitai Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 81bccf11-a501-4967-8f04-6a0768f13528

📥 Commits

Reviewing files that changed from the base of the PR and between 38b5418 and dcf1558.

⛔ Files ignored due to path filters (1)
  • examples/portaljs-catalog/public/data/region-metrics.parquet is excluded by !**/*.parquet
📒 Files selected for processing (3)
  • examples/portaljs-catalog/.gitattributes
  • examples/portaljs-catalog/README.md
  • examples/portaljs-catalog/datasets.json
✅ Files skipped from review due to trivial changes (1)
  • examples/portaljs-catalog/README.md

📝 Walkthrough

Walkthrough

Adds PMTiles map preview support in the core package and the catalog example, wires pmtiles into dataset rendering, and updates sample data metadata, dependencies, and documentation.

Changes

PMTiles map preview feature

Layer / File(s) Summary
Core MapPreview component implementation
packages/core/src/ui/MapPreview/MapPreview.tsx, packages/core/src/ui/MapPreview/index.ts, packages/core/src/ui/index.ts, packages/core/package.json, packages/core/README.md
Adds the core MapPreview API, PMTiles/MapLibre client-side rendering, inspection and zoom UI, UI re-exports, runtime dependencies, and component docs.
Example app MapPreview component
examples/portaljs-catalog/components/MapPreview.tsx
Adds the catalog-local PMTiles/MapLibre preview component with loading, fallback, inspection, and map controls.
pmtiles DataFormat and resource preview wiring
examples/portaljs-catalog/lib/providers/types.ts, examples/portaljs-catalog/pages/[owner]/[slug].tsx
Adds pmtiles to the dataset format union and renders MapPreview for pmtiles resources in the dataset page flow.
Sample data, dependencies, and docs
examples/portaljs-catalog/.gitattributes, examples/portaljs-catalog/datasets.json, examples/portaljs-catalog/package.json, examples/portaljs-catalog/README.md
Updates bundled data handling, dataset metadata, runtime dependencies, and README guidance for PMTiles and map preview usage.

Estimated code review effort: 4 (Complex) | ~60 minutes

Possibly related PRs

  • datopian/portaljs#1536: Extends the dataset showcase page structure that this PR updates to render PMTiles previews.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: adding a serverless MapPreview for PMTiles/MapLibre in core and the template.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch polecat/furiosa/po-6sr.1@mrak0qx3

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (2)
examples/portaljs-catalog/components/MapPreview.tsx (1)

1-389: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚖️ Poor tradeoff

Near-complete duplication of packages/core's MapPreview.

This component reimplements ~90% of packages/core/src/ui/MapPreview/MapPreview.tsx (protocol registration, header/metadata reads, layer construction, click-to-inspect, degrade-to-offline logic) rather than consuming the core export. This is presumably driven by the Tailwind-vs-inline-styles guideline split, but it means every future bug fix (e.g., the attribution/PMTiles-instance-sharing issues below) must be applied twice.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@examples/portaljs-catalog/components/MapPreview.tsx` around lines 1 - 389,
This MapPreview implementation is duplicating most of the logic already present
in packages/core’s MapPreview, so update it to reuse the core component or
shared helper/hook instead of reimplementing protocol setup, PMTiles loading,
layer wiring, and inspection behavior. Keep the portal-specific styling
differences isolated, but move the common map lifecycle into the shared
MapPreview path so fixes only need to be made once and symbols like MapPreview,
injectBaseCss, and the map initialization effect stay aligned.
packages/core/src/ui/MapPreview/MapPreview.tsx (1)

113-128: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Duplicate PMTiles instance/requests — share via protocol.add().

A standalone PMTiles instance is created to read the header/metadata, but the vector source URL pmtiles://${absoluteUrl} causes the pmtiles protocol to independently resolve its own instance for the same archive, duplicating the initial directory/header fetches. The PMTiles docs recommend registering the instance on the protocol so both paths share one reader.

♻️ Share one PMTiles instance
+      const protocol = new pmtiles.Protocol();
       const anyGl = maplibregl as unknown as {
         __portaljsPmtilesProtocol?: boolean;
       };
       if (!anyGl.__portaljsPmtilesProtocol) {
-        maplibregl.addProtocol("pmtiles", new pmtiles.Protocol().tile);
+        maplibregl.addProtocol("pmtiles", protocol.tile);
         anyGl.__portaljsPmtilesProtocol = true;
       }
       ...
       const archive = new pmtiles.PMTiles(absoluteUrl);
+      protocol.add(archive);

Also applies to: 186-191

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/core/src/ui/MapPreview/MapPreview.tsx` around lines 113 - 128, The
MapPreview PMTiles setup is creating a separate PMTiles reader for
header/metadata while the vector source URL is resolved through the pmtiles
protocol, causing duplicate archive requests. In MapPreview.tsx, update the
PMTiles initialization around the archive/header loading logic and the source
setup so the same `pmtiles.PMTiles` instance is registered through
`protocol.add()` and reused by the `pmtiles://${absoluteUrl}` source. Also apply
the same shared-instance approach in the later source creation path referenced
by the review so both code paths use one reader.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@examples/portaljs-catalog/components/MapPreview.tsx`:
- Around line 94-131: In MapPreview, reuse the same PMTiles instance for both
header/metadata reads and the pmtiles:// vector source by registering the
created archive with protocol.add(archive) before constructing the map, instead
of letting the protocol create a second instance. Also remove the unconditional
attributionControl: false from the map setup so the default basemap attribution
remains visible, since this component always uses the demo basemap and has no
basemap={false} opt-out.

In `@examples/portaljs-catalog/pages/`[owner]/[slug].tsx:
- Around line 27-33: The map preview currently relies on the MapLibre/PMTiles
stack via MapPreview, which conflicts with the approved react-leaflet/leaflet
guideline. Update the implementation around MapPreview and the PMTiles rendering
path to use react-leaflet/leaflet directly for map/GeoJSON rendering, or if
MapLibre + PMTiles is intentionally required, explicitly revise the coding
guidelines to allow this stack. Keep the dynamic client-only loading pattern
intact while swapping the underlying map implementation to the sanctioned
approach.

In `@packages/core/src/ui/MapPreview/MapPreview.tsx`:
- Around line 52-58: The lint failures are coming from missing browser globals
in the ESLint setup, not from the MapPreview logic itself. Update the ESLint
config used by the packages/core scope so DOM symbols referenced in
MapPreview.tsx and related client-only code like document, window, URL, and
HTMLDivElement are recognized (for example by enabling the browser environment
or equivalent DOM globals). Keep the fix in the config rather than changing
injectBaseCss or the other client-side references.
- Around line 140-174: MapPreview is suppressing required basemap attribution by
disabling MapLibre’s attribution control, and the custom attribution prop is
only rendered as plain text. Update MapPreview so the default basemap still
shows MapLibre/OSM attribution (or merge basemap attribution with the caller’s
data-source credit), and ensure the attribution prop in MapPreview is rendered
in a way that preserves any intended link/HTML formatting.

---

Nitpick comments:
In `@examples/portaljs-catalog/components/MapPreview.tsx`:
- Around line 1-389: This MapPreview implementation is duplicating most of the
logic already present in packages/core’s MapPreview, so update it to reuse the
core component or shared helper/hook instead of reimplementing protocol setup,
PMTiles loading, layer wiring, and inspection behavior. Keep the portal-specific
styling differences isolated, but move the common map lifecycle into the shared
MapPreview path so fixes only need to be made once and symbols like MapPreview,
injectBaseCss, and the map initialization effect stay aligned.

In `@packages/core/src/ui/MapPreview/MapPreview.tsx`:
- Around line 113-128: The MapPreview PMTiles setup is creating a separate
PMTiles reader for header/metadata while the vector source URL is resolved
through the pmtiles protocol, causing duplicate archive requests. In
MapPreview.tsx, update the PMTiles initialization around the archive/header
loading logic and the source setup so the same `pmtiles.PMTiles` instance is
registered through `protocol.add()` and reused by the `pmtiles://${absoluteUrl}`
source. Also apply the same shared-instance approach in the later source
creation path referenced by the review so both code paths use one reader.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: f3293f2c-d577-4b90-9e4f-81ff1d592719

📥 Commits

Reviewing files that changed from the base of the PR and between 1ccbece and 38b5418.

⛔ Files ignored due to path filters (2)
  • examples/portaljs-catalog/package-lock.json is excluded by !**/package-lock.json
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (13)
  • examples/portaljs-catalog/.gitattributes
  • examples/portaljs-catalog/README.md
  • examples/portaljs-catalog/components/MapPreview.tsx
  • examples/portaljs-catalog/datasets.json
  • examples/portaljs-catalog/lib/providers/types.ts
  • examples/portaljs-catalog/package.json
  • examples/portaljs-catalog/pages/[owner]/[slug].tsx
  • examples/portaljs-catalog/public/data/world-boundaries.pmtiles
  • packages/core/README.md
  • packages/core/package.json
  • packages/core/src/ui/MapPreview/MapPreview.tsx
  • packages/core/src/ui/MapPreview/index.ts
  • packages/core/src/ui/index.ts

Comment on lines +94 to +131
const archive = new pmtiles.PMTiles(absoluteUrl)
let header: Awaited<ReturnType<typeof archive.getHeader>>
let vectorLayers: { id: string }[]
try {
header = await archive.getHeader()
const metadata = (await archive.getMetadata()) as {
vector_layers?: { id: string }[]
}
vectorLayers = metadata?.vector_layers ?? []
} catch {
if (!cancelled) {
setError(`Could not read the PMTiles archive at ${url}.`)
setLoading(false)
}
return
}
if (cancelled || !containerRef.current) return

const offlineStyle: StyleSpecification = {
version: 8,
sources: {},
layers: [
// Matches the cream-panel canvas so a basemap-less map still looks placed.
{ id: 'background', type: 'background', paint: { 'background-color': '#ece7db' } },
],
}

try {
map = new maplibregl.Map({
container: containerRef.current,
style: BASEMAP_STYLE,
center: [
(header.minLon + header.maxLon) / 2,
(header.minLat + header.maxLat) / 2,
],
zoom: 1,
attributionControl: false,
})

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Same duplicate-PMTiles-instance and dropped-attribution issues as the core component.

  • Lines 94-131: a standalone PMTiles instance reads header/metadata, while the pmtiles://${absoluteUrl} vector source (Line 161) causes the protocol to create a second instance for the same archive — share via protocol.add(archive) as flagged in packages/core/src/ui/MapPreview/MapPreview.tsx (Lines 113-128).
  • Line 130: attributionControl: false drops the default basemap's required OSM attribution; this component has no basemap={false} escape hatch either, so it always shows the demo basemap without attribution.

Also applies to: 160-162

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@examples/portaljs-catalog/components/MapPreview.tsx` around lines 94 - 131,
In MapPreview, reuse the same PMTiles instance for both header/metadata reads
and the pmtiles:// vector source by registering the created archive with
protocol.add(archive) before constructing the map, instead of letting the
protocol create a second instance. Also remove the unconditional
attributionControl: false from the map setup so the default basemap attribution
remains visible, since this component always uses the demo basemap and has no
basemap={false} opt-out.

Comment on lines +27 to +33
// MapLibre GL touches `window` at module scope, so the map preview is likewise
// client-only. The chunk (maplibre + pmtiles) loads only when a dataset
// actually has a PMTiles resource.
const MapPreview = dynamic(() => import('../../components/MapPreview'), {
ssr: false,
})

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟠 Major | 🏗️ Heavy lift

Map stack deviates from coding guidelines (MapLibre/PMTiles vs. react-leaflet/leaflet).

The comment confirms MapLibre GL is used for map rendering, and the MapPreview component (loaded here and rendered for pmtiles resources) is built on maplibre-gl + pmtiles. The coding guidelines mandate react-leaflet/leaflet directly for GeoJSON/map rendering. This PR introduces a different map stack entirely, which the guidelines don't sanction.

If MapLibre + PMTiles is intentionally chosen for vector-tile/PMTiles support (which react-leaflet doesn't natively provide), the guideline should be updated to reflect the new approved stack; otherwise the map rendering should be reworked onto react-leaflet/leaflet per the existing policy.

As per coding guidelines: "Add react-leaflet and leaflet directly for GeoJSON/map rendering; do not bundle or import map components from @portaljs/components."

Also applies to: 448-452

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@examples/portaljs-catalog/pages/`[owner]/[slug].tsx around lines 27 - 33, The
map preview currently relies on the MapLibre/PMTiles stack via MapPreview, which
conflicts with the approved react-leaflet/leaflet guideline. Update the
implementation around MapPreview and the PMTiles rendering path to use
react-leaflet/leaflet directly for map/GeoJSON rendering, or if MapLibre +
PMTiles is intentionally required, explicitly revise the coding guidelines to
allow this stack. Keep the dynamic client-only loading pattern intact while
swapping the underlying map implementation to the sanctioned approach.

Source: Coding guidelines

Comment on lines +52 to +58
function injectBaseCss() {
const id = "portaljs-map-preview-css";
if (document.getElementById(id)) return;
const style = document.createElement("style");
style.id = id;
style.textContent = BASE_CSS;
document.head.appendChild(style);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

ESLint no-undef errors for DOM globals will likely fail lint in CI.

Static analysis flags document, HTMLDivElement, URL, and window as undefined. The code is correct (these only run client-side inside useEffect), but the eslint config for this file/package appears to be missing browser globals (e.g., env: browser or DOM lib globals). This is a config gap, not a logic bug, but will surface as real lint failures.

🔧 Likely fix (adjust eslint config, not this file)
// .eslintrc.js (or equivalent) for packages/core
module.exports = {
  env: { browser: true, es2021: true },
  // ...
};

Also applies to: 77-77, 111-111

🧰 Tools
🪛 ESLint

[error] 54-54: 'document' is not defined.

(no-undef)


[error] 55-55: 'document' is not defined.

(no-undef)


[error] 58-58: 'document' is not defined.

(no-undef)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/core/src/ui/MapPreview/MapPreview.tsx` around lines 52 - 58, The
lint failures are coming from missing browser globals in the ESLint setup, not
from the MapPreview logic itself. Update the ESLint config used by the
packages/core scope so DOM symbols referenced in MapPreview.tsx and related
client-only code like document, window, URL, and HTMLDivElement are recognized
(for example by enabling the browser environment or equivalent DOM globals).
Keep the fix in the config rather than changing injectBaseCss or the other
client-side references.

Source: Linters/SAST tools

Comment on lines +140 to +174
map = new maplibregl.Map({
container: containerRef.current,
style: basemap === false ? offlineStyle : basemap,
center: center ?? [
(header.minLon + header.maxLon) / 2,
(header.minLat + header.maxLat) / 2,
],
zoom: zoom ?? 1,
attributionControl: false,
});
} catch (e) {
// Most commonly WebGL being unavailable (old hardware, disabled in
// the browser) — say so instead of a generic failure.
if (!cancelled) setError(e instanceof Error ? e.message : "Could not initialize the map.");
return;
}
mapRef.current = map;

// A missing/unreachable basemap must never take the data layer down
// with it: swap to the offline background and carry on. Match only
// network-shaped failures before the style is up — internal maplibre
// errors (e.g. "Style is not done loading") must NOT trigger it.
let degraded = false;
map.on("error", (e: { error?: Error & { status?: number } }) => {
const message = e.error?.message ?? "";
if (
!degraded &&
basemap !== false &&
!map?.isStyleLoaded() &&
/fetch|network|ajax/i.test(message)
) {
degraded = true;
map?.setStyle(offlineStyle);
}
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Default demo basemap attribution is silently dropped.

attributionControl: false disables MapLibre's built-in attribution UI entirely. The default basemap (demotiles.maplibre.org) is backed by OpenStreetMap-derived data whose licensing expects visible attribution, and the attribution prop is documented purely as the caller's data-source credit, not basemap credit — so when a caller uses the default basemap and sets attribution="Natural Earth", no OSM/MapLibre attribution is shown anywhere. Also note the attribution prop is rendered as a plain text node (Line 453), so even an HTML attribution string (as pmtiles/MapLibre examples recommend) won't render as a link.

🛠️ Keep attribution control (or merge basemap + data attribution)
-        map = new maplibregl.Map({
-          container: containerRef.current,
-          style: basemap === false ? offlineStyle : basemap,
-          center: center ?? [
-            (header.minLon + header.maxLon) / 2,
-            (header.minLat + header.maxLat) / 2,
-          ],
-          zoom: zoom ?? 1,
-          attributionControl: false,
-        });
+        map = new maplibregl.Map({
+          container: containerRef.current,
+          style: basemap === false ? offlineStyle : basemap,
+          center: center ?? [
+            (header.minLon + header.maxLon) / 2,
+            (header.minLat + header.maxLat) / 2,
+          ],
+          zoom: zoom ?? 1,
+          attributionControl: basemap !== false,
+        });

Also applies to: 438-455

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/core/src/ui/MapPreview/MapPreview.tsx` around lines 140 - 174,
MapPreview is suppressing required basemap attribution by disabling MapLibre’s
attribution control, and the custom attribution prop is only rendered as plain
text. Update MapPreview so the default basemap still shows MapLibre/OSM
attribution (or merge basemap attribution with the caller’s data-source credit),
and ensure the attribution prop in MapPreview is rendered in a way that
preserves any intended link/HTML formatting.

… (po-6sr.1)

Migrate the two bundled binary samples (region-metrics.parquet,
world-boundaries.pmtiles) off raw git commit onto R2 via Giftless,
referenced by data.portaljs.com URLs, so the showcase demonstrates the
real serverless range-read path and the repo carries no binary blobs.
CSVs stay inline for zero-cred offline dev. Updates .gitattributes,
datasets.json, README.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@anuveyatsu anuveyatsu merged commit c90db3c into main Jul 7, 2026
14 of 15 checks passed
@anuveyatsu anuveyatsu deleted the polecat/furiosa/po-6sr.1@mrak0qx3 branch July 7, 2026 17:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

1 participant