feat(desktop): render ```map fenced blocks as interactive maps

Messages and channel canvases can now embed a live map by writing a
```map block containing a small JSON spec (center/zoom, markers, lines).
Agents can drive it with the existing `buzz canvas set` / messages
commands, no new protocol or permissions needed.

MapLibre GL with OpenFreeMap tiles (no API key), lazy-loaded so the
~1 MB chunk only downloads when a map is rendered.

Signed-off-by: Wintermute <a00fdff03f06355c4fc6d6268f3a09ebe752c20b98df8fdab5fe81e266d6630a@meshllm.communities.buzz.xyz>
This commit is contained in:
Wintermute
2026-10-09 13:14:59 -04:00
parent 16eb0b6685
commit b93504ae49
6 changed files with 496 additions and 0 deletions

View File

@@ -69,6 +69,7 @@
"jdenticon": "^3.3.0",
"linkifyjs": "^4.3.2",
"lucide-react": "^1.0.0",
"maplibre-gl": "^5.24.0",
"mdast-util-from-markdown": "^2.0.3",
"motion": "^12.38.0",
"qrcode": "^1.5.4",

View File

@@ -32,6 +32,8 @@ export const CODE_BLOCK_CLASS =
"code-block-lines block min-w-full whitespace-pre font-mono text-sm font-medium text-foreground";
const DIFF_ADD_RE = /\s*\/\/\s*\[!code\s*\+\+\]\s*$/;
const DIFF_REMOVE_RE = /\s*\/\/\s*\[!code\s*--\]\s*$/;
// maplibre is heavy; only load it when a message or canvas contains a map.
const MapBlock = React.lazy(() => import("./MapBlock"));
function ensureHighlighter(): Promise<void> {
if (shikiHighlighter) return Promise.resolve();
@@ -96,6 +98,14 @@ export function MarkdownCodeBlock({
[code],
);
if (language === "map") {
return (
<React.Suspense fallback={null}>
<MapBlock code={code} />
</React.Suspense>
);
}
return (
<div className="group relative" data-code-block="">
<pre

View File

@@ -0,0 +1,102 @@
import maplibregl from "maplibre-gl";
import "maplibre-gl/dist/maplibre-gl.css";
import * as React from "react";
import { useTheme } from "@/shared/theme/ThemeProvider";
import { type MapSpec, mapSpecPoints, parseMapSpec } from "./mapSpec";
// OpenFreeMap: free vector tiles, no API key.
const STYLE_URL = {
light: "https://tiles.openfreemap.org/styles/liberty",
dark: "https://tiles.openfreemap.org/styles/dark",
};
const DEFAULT_COLOR = "#3b82f6";
const DEFAULT_HEIGHT = 360;
function safeColor(color: string | undefined) {
return color && CSS.supports("color", color) ? color : DEFAULT_COLOR;
}
function addOverlays(map: maplibregl.Map, spec: MapSpec) {
for (const marker of spec.markers) {
const pin = new maplibregl.Marker({ color: safeColor(marker.color) })
.setLngLat([marker.lng, marker.lat])
.addTo(map);
if (marker.label) {
pin.getElement().title = marker.label;
pin.setPopup(new maplibregl.Popup({ offset: 24 }).setText(marker.label));
}
}
if (spec.lines.length === 0) return;
map.addSource("buzz-lines", {
type: "geojson",
data: {
type: "FeatureCollection",
features: spec.lines.map((line) => ({
type: "Feature",
properties: { color: safeColor(line.color) },
geometry: { type: "LineString", coordinates: line.coordinates },
})),
},
});
map.addLayer({
id: "buzz-lines",
type: "line",
source: "buzz-lines",
layout: { "line-cap": "round", "line-join": "round" },
paint: { "line-color": ["get", "color"], "line-width": 4 },
});
}
function MapView({ spec }: { spec: MapSpec }) {
const containerRef = React.useRef<HTMLDivElement | null>(null);
const { isDark } = useTheme();
React.useEffect(() => {
const container = containerRef.current;
if (!container) return;
const points = mapSpecPoints(spec);
const map = new maplibregl.Map({
container,
style: STYLE_URL[isDark ? "dark" : "light"],
center: spec.center ?? points[0],
zoom: spec.zoom ?? 12,
attributionControl: { compact: true },
cooperativeGestures: true,
});
map.addControl(new maplibregl.NavigationControl(), "top-right");
if (!spec.center && points.length > 1) {
const bounds = new maplibregl.LngLatBounds(points[0], points[0]);
for (const point of points) bounds.extend(point);
map.fitBounds(bounds, {
padding: 48,
maxZoom: spec.zoom ?? 15,
duration: 0,
});
}
map.on("load", () => addOverlays(map, spec));
return () => map.remove();
}, [spec, isDark]);
return (
<div
className="overflow-hidden rounded-2xl border border-border/70"
data-testid="markdown-map-block"
ref={containerRef}
style={{ height: spec.height ?? DEFAULT_HEIGHT }}
/>
);
}
export default function MapBlock({ code }: { code: string }) {
const result = React.useMemo(() => parseMapSpec(code), [code]);
if (!result.ok) {
return (
<p className="rounded-xl border border-destructive/30 bg-destructive/10 px-3 py-2 text-sm text-destructive">
{result.error}
</p>
);
}
return <MapView spec={result.spec} />;
}

View File

@@ -0,0 +1,69 @@
import assert from "node:assert/strict";
import test from "node:test";
import { mapSpecPoints, parseMapSpec } from "./mapSpec.ts";
test("parses markers, lines, and viewport", () => {
const result = parseMapSpec(
JSON.stringify({
center: [-122.42, 37.77],
zoom: 30,
height: 5000,
markers: [{ lng: -122.4, lat: 37.78, label: "HQ", color: "#f00" }],
lines: [
{
coordinates: [
[-122.4, 37.78],
[-122.41, 37.76],
],
},
],
}),
);
assert.equal(result.ok, true);
assert.deepEqual(result.spec, {
center: [-122.42, 37.77],
zoom: 22,
height: 800,
markers: [{ lng: -122.4, lat: 37.78, label: "HQ", color: "#f00" }],
lines: [
{
coordinates: [
[-122.4, 37.78],
[-122.41, 37.76],
],
label: undefined,
color: undefined,
},
],
});
assert.deepEqual(mapSpecPoints(result.spec), [
[-122.4, 37.78],
[-122.4, 37.78],
[-122.41, 37.76],
]);
});
test("markers alone are enough to frame the map", () => {
const result = parseMapSpec('{"markers":[{"lng":1,"lat":2}]}');
assert.equal(result.ok, true);
assert.equal(result.spec.center, undefined);
});
test("rejects malformed specs with a readable error", () => {
assert.deepEqual(parseMapSpec("{nope"), {
ok: false,
error: "Map block is not valid JSON.",
});
assert.equal(parseMapSpec("[]").ok, false);
assert.equal(parseMapSpec("{}").ok, false);
assert.equal(parseMapSpec('{"center":[200,0]}').ok, false);
assert.equal(
parseMapSpec('{"markers":[{"lng":"x","lat":1}]}').error,
"markers[0] needs valid lng/lat.",
);
assert.equal(
parseMapSpec('{"lines":[{"coordinates":[[0,0]]}]}').error,
"lines[0] needs at least two [lng, lat] coordinates.",
);
});

View File

@@ -0,0 +1,135 @@
/**
* Spec for ```map fenced blocks. Authored by people or agents (for example
* via `buzz canvas set`) as JSON; coordinates are [lng, lat] like GeoJSON.
*/
export type LngLat = [number, number];
export type MapMarker = {
lng: number;
lat: number;
label?: string;
color?: string;
};
export type MapLine = {
coordinates: LngLat[];
label?: string;
color?: string;
};
export type MapSpec = {
center?: LngLat;
zoom?: number;
height?: number;
markers: MapMarker[];
lines: MapLine[];
};
export type MapSpecResult =
| { ok: true; spec: MapSpec }
| { ok: false; error: string };
const MIN_HEIGHT = 160;
const MAX_HEIGHT = 800;
function isLngLat(value: unknown): value is LngLat {
return (
Array.isArray(value) &&
value.length === 2 &&
Number.isFinite(value[0]) &&
Number.isFinite(value[1]) &&
Math.abs(value[0]) <= 180 &&
Math.abs(value[1]) <= 90
);
}
function optionalString(value: unknown): string | undefined {
return typeof value === "string" && value.trim() ? value : undefined;
}
export function parseMapSpec(source: string): MapSpecResult {
let raw: unknown;
try {
raw = JSON.parse(source);
} catch {
return { ok: false, error: "Map block is not valid JSON." };
}
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
return { ok: false, error: "Map block must be a JSON object." };
}
const input = raw as Record<string, unknown>;
const markers: MapMarker[] = [];
for (const [index, item] of (Array.isArray(input.markers)
? input.markers
: []
).entries()) {
const marker = (item ?? {}) as Record<string, unknown>;
if (!isLngLat([marker.lng, marker.lat])) {
return { ok: false, error: `markers[${index}] needs valid lng/lat.` };
}
markers.push({
lng: marker.lng as number,
lat: marker.lat as number,
label: optionalString(marker.label),
color: optionalString(marker.color),
});
}
const lines: MapLine[] = [];
for (const [index, item] of (Array.isArray(input.lines)
? input.lines
: []
).entries()) {
const line = (item ?? {}) as Record<string, unknown>;
const coordinates = line.coordinates;
if (
!Array.isArray(coordinates) ||
coordinates.length < 2 ||
!coordinates.every(isLngLat)
) {
return {
ok: false,
error: `lines[${index}] needs at least two [lng, lat] coordinates.`,
};
}
lines.push({
coordinates,
label: optionalString(line.label),
color: optionalString(line.color),
});
}
if (input.center !== undefined && !isLngLat(input.center)) {
return { ok: false, error: "center must be [lng, lat]." };
}
if (input.center === undefined && markers.length + lines.length === 0) {
return { ok: false, error: "Map block needs a center, markers, or lines." };
}
const zoom = Number.isFinite(input.zoom)
? Math.min(22, Math.max(0, input.zoom as number))
: undefined;
const height = Number.isFinite(input.height)
? Math.min(MAX_HEIGHT, Math.max(MIN_HEIGHT, input.height as number))
: undefined;
return {
ok: true,
spec: {
center: input.center as LngLat | undefined,
zoom,
height,
markers,
lines,
},
};
}
/** Every coordinate in the spec, used to fit the initial viewport. */
export function mapSpecPoints(spec: MapSpec): LngLat[] {
return [
...spec.markers.map((marker): LngLat => [marker.lng, marker.lat]),
...spec.lines.flatMap((line) => line.coordinates),
];
}

179
pnpm-lock.yaml generated
View File

@@ -198,6 +198,9 @@ importers:
lucide-react:
specifier: ^1.0.0
version: 1.16.0(react@19.2.8)
maplibre-gl:
specifier: ^5.24.0
version: 5.24.0
mdast-util-from-markdown:
specifier: ^2.0.3
version: 2.0.3
@@ -710,6 +713,42 @@ packages:
'@jridgewell/trace-mapping@0.3.31':
resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==}
'@mapbox/jsonlint-lines-primitives@2.0.3':
resolution: {integrity: sha512-0SElaV0uMxEnxzBhhX9WTuPyUeMsAN/SS0i16tjuba4/mio63MG9khjC1a0JAiPGXAwvwm4UfHJURCN7nyudQg==}
engines: {node: '>= 22'}
'@mapbox/point-geometry@1.1.0':
resolution: {integrity: sha512-YGcBz1cg4ATXDCM/71L9xveh4dynfGmcLDqufR+nQQy3fKwsAZsWd/x4621/6uJaeB9mwOHE6hPeDgXz9uViUQ==}
'@mapbox/tiny-sdf@2.3.0':
resolution: {integrity: sha512-gBvcNDBBB66bgQYvnaJ6Jk+VCaMmVq5+IZR2kWPisT88Z8s0kBQt6hfVi8QgRW8Vpg0UWZzBA4ZSyzHHC+VeRA==}
'@mapbox/unitbezier@0.0.1':
resolution: {integrity: sha512-nMkuDXFv60aBr9soUG5q+GvZYL+2KZHVvsqFCzqnkGEf46U2fvmytHaEVc1/YZbiLn8X+eR3QzX1+dwDO1lxlw==}
'@mapbox/unitbezier@1.0.0':
resolution: {integrity: sha512-fqd515fjBmANKGGsQ286E2Wvj/XvDFpGzwJxq4CI6jMQue6Oy04uCKp+JWKF00xRTmk6cEu1jPJ9p3xqH8YWqQ==}
'@mapbox/vector-tile@2.0.5':
resolution: {integrity: sha512-pXj8m7KTsqZt+1jsE0xIpGvqTSbblfkuEJL/NJmNePMtEwxO8V3XMDo9WMSfDeqHvCtBI9Lmt4mGcGR10zecmw==}
'@mapbox/whoots-js@3.1.0':
resolution: {integrity: sha512-Es6WcD0nO5l+2BOQS4uLfNPYQaNDfbot3X1XUoloz+x0mPDS3eeORZJl06HXjwBG1fOGwCRnzK88LMdxKRrd6Q==}
engines: {node: '>=6.0.0'}
'@maplibre/geojson-vt@6.1.2':
resolution: {integrity: sha512-2ZYhobdAY+y4/P/z1xe0698165LywD0yDdWugdu3NRUrajgVWut+tLkcz7i/RJguMTURwK7gxwmwYMFJbgbQFw==}
'@maplibre/maplibre-gl-style-spec@24.10.0':
resolution: {integrity: sha512-lichxSiagMEBBrqHF0trtMQH9RKh+9jUlIJl0qW0QHvt2H/tbvUWdE+ZzI2Jd0/pT7j/iavLonlPu7EQ/ixTOw==}
hasBin: true
'@maplibre/mlt@1.3.0':
resolution: {integrity: sha512-7M2O7ABM80Oi8XPkfkrjKB8mOBIxGoVgkA52Mu4RMWoBvvMdagQ522+WxVYujZsMpqIr3BpzrTbXrm4k8lCzCQ==}
'@maplibre/vt-pbf@4.3.2':
resolution: {integrity: sha512-j6p0AdjvAR19Z3XaCysle7A4ZSo08tYOzxD0Y9NQylwPAkwJJeYub5b2eVucdeDh7erhv69DahoLOevDRERRUw==}
'@mediapipe/tasks-vision@0.10.35':
resolution: {integrity: sha512-HOvadwVRE6JC+45nyYhmnywnr5h/J8KZvOeUNVOG9q/0875pZgItznFB9bRTvLc264YSJqiZ1NsIpCStJw/egg==}
@@ -2076,6 +2115,9 @@ packages:
'@types/estree@1.0.9':
resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==}
'@types/geojson@7946.0.16':
resolution: {integrity: sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==}
'@types/hast@3.0.4':
resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==}
@@ -2415,6 +2457,9 @@ packages:
resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==}
engines: {node: '>= 0.4'}
earcut@3.2.4:
resolution: {integrity: sha512-FHc6yNpnTORB0tM5kmZ03iD2mDuNakDE2lvf4Hw1XkfxtEHIGVi01B/GqUSwx+NcC1WGjidAfpTXzeALtPtfpg==}
electron-to-chromium@1.5.361:
resolution: {integrity: sha512-Q6Hts7N9FnJc5LeGRINFvLhCI9xZmNtTDe5ZbcVezQz7cU4a8Aua3GH1b8J2XY8Al9PF+OCwYqhgsOOheMdvkA==}
@@ -2567,6 +2612,9 @@ packages:
gitdiff-parser@0.3.1:
resolution: {integrity: sha512-YQJnY8aew65id8okGxKCksH3efDCJ9HzV7M9rsvd65habf39Pkh4cgYJ27AaoDMqo1X98pgNJhNMrm/kpV7UVQ==}
gl-matrix@3.4.4:
resolution: {integrity: sha512-latSnyDNt/8zYUB6VIJ6PCh2jBjJX6gnDsoCZ7LyW7GkqrD51EWwa9qCoGixj8YqBtETQK/xY7OmpTF8xz1DdQ==}
gopd@1.2.0:
resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==}
engines: {node: '>= 0.4'}
@@ -2705,6 +2753,9 @@ packages:
engines: {node: '>=6'}
hasBin: true
json-stringify-pretty-compact@4.0.0:
resolution: {integrity: sha512-3CNZ2DnrpByG9Nqj6Xo8vqbjT4F6N+tb4Gb28ESAZjYZ5yqvmc56J+/kuIwkaAMOyblTQhUW7PxMkUb8Q36N3Q==}
json5@2.2.3:
resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==}
engines: {node: '>=6'}
@@ -2716,6 +2767,9 @@ packages:
just-once@1.1.0:
resolution: {integrity: sha512-+rZVpl+6VyTilK7vB/svlMPil4pxqIJZkbnN7DKZTOzyXfun6ZiFeq2Pk4EtCEHZ0VU4EkdFzG8ZK5F3PErcDw==}
kdbush@4.1.0:
resolution: {integrity: sha512-e9vurzrXJQrFX6ckpHP3bvj5l+9CnYzkxDNnNQ1h2QTqdWsUAJgXiKdGNcOa1EY85dU8KbQ+z/FdQdB7P+9yfQ==}
lightningcss-android-arm64@1.32.0:
resolution: {integrity: sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==}
engines: {node: '>= 12.0.0'}
@@ -2829,6 +2883,10 @@ packages:
magic-string@0.30.21:
resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==}
maplibre-gl@5.24.0:
resolution: {integrity: sha512-ALyFxgtd5R+65UqZ/++lOqwWcC0SNho9c27fYSyLmG7AfnAul2o46F05aDJGPbFU57wos9dgcIySHs0Xe6ia3A==}
engines: {node: '>=16.14.0', npm: '>=8.1.0'}
markdown-it-task-lists@2.1.1:
resolution: {integrity: sha512-TxFAc76Jnhb2OUu+n3yz9RMu4CwGfaT788br6HhEDlvWfdeJcLUsxk1Hgw2yJio0OXsxv7pyIPmvECY7bMbluA==}
@@ -3018,6 +3076,9 @@ packages:
ms@2.1.3:
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
murmurhash-js@1.0.0:
resolution: {integrity: sha512-TvmkNhkv8yct0SVBSy+o8wYzXjE4Zz3PCesbfs8HiCXXdcTuocApFv11UWlNFWKYsP2okqrhb7JNlSm9InBhIw==}
nanoid@3.3.16:
resolution: {integrity: sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q==}
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
@@ -3087,6 +3148,14 @@ packages:
pathe@2.0.3:
resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==}
pbf@4.0.2:
resolution: {integrity: sha512-J0ajxARhZfpUEebxYs1vhMGMuLSXtBe1e+fFPDrf2uA2hgo+UshKfNUWOz92HJNz6/NFEXseQPddnHkTreWRqg==}
hasBin: true
pbf@5.1.2:
resolution: {integrity: sha512-mnvGdvOrIvJOBGUEdGkrVXjN8E/VkIJCkf2eS1DH2yv82ORUlLttmDt0rWY38yYZmVwciZwBUvHM20qxBZf40w==}
hasBin: true
picocolors@1.1.1:
resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
@@ -3132,6 +3201,9 @@ packages:
resolution: {integrity: sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ==}
engines: {node: ^10 || ^12 || >=14}
potpack@2.1.0:
resolution: {integrity: sha512-pcaShQc1Shq0y+E7GqJqvZj8DTthWV1KeHGdi0Z6IAin2Oi3JnLCOfwnCo84qc+HAp52wT9nK9H7FAJp5a44GQ==}
prettier@3.8.3:
resolution: {integrity: sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw==}
engines: {node: '>=14'}
@@ -3187,6 +3259,9 @@ packages:
prosemirror-view@1.41.8:
resolution: {integrity: sha512-TnKDdohEatgyZNGCDWIdccOHXhYloJwbwU+phw/a23KBvJIR9lWQWW7WHHK3vBdOLDNuF7TaX98GObUZOWkOnA==}
protocol-buffers-schema@3.6.1:
resolution: {integrity: sha512-VG2K63Igkiv9p76tk1lilczEK1cT+kCjKtkdhw1dQZV3k3IXJbd3o6Ho8b9zJZaHSnT2hKe4I+ObmX9w6m5SmQ==}
punycode.js@2.3.1:
resolution: {integrity: sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==}
engines: {node: '>=6'}
@@ -3205,6 +3280,9 @@ packages:
engines: {node: '>=10.13.0'}
hasBin: true
quickselect@3.0.0:
resolution: {integrity: sha512-XdjUArbK4Bm5fLLvlm5KpTFOiOThgfWWI4axAZDWg4E/0mKdZyI9tNEfds27qCi1ze/vwTR16kvmmGhRra3c2g==}
react-day-picker@10.0.1:
resolution: {integrity: sha512-eNh6BlwcYInWaJtRv18mXQ06Ys/H6rdTZAnTaSdOYJuTpwP1JMCHNd1FDRadA+gbeinq+psdULN5Xnowy9mV8w==}
engines: {node: '>=18'}
@@ -3315,6 +3393,9 @@ packages:
require-main-filename@2.0.0:
resolution: {integrity: sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==}
resolve-protobuf-schema@2.1.0:
resolution: {integrity: sha512-kI5ffTiZWmJaS/huM8wZfEMer1eRd7oJQhDuxeCLe3t7N7mX3z94CN0xPxBQxFYQTSNz9T0i+v6inKqSdK8xrQ==}
rolldown@1.0.3:
resolution: {integrity: sha512-i00lAJ2ks1BYr7rjNjKC7BcqAS7nVfiT3QX1SI5aY+AFHblCmaUf9OE9dbdzDvW6dJxbi2ZCZiy9v3CcwOiX3g==}
engines: {node: ^20.19.0 || >=22.12.0}
@@ -3446,6 +3527,9 @@ packages:
resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==}
engines: {node: '>=12.0.0'}
tinyqueue@3.0.0:
resolution: {integrity: sha512-gRa9gwYU3ECmQYv3lslts5hxuIa90veaEcxDYuu3QGOIAEM2mOZkVHp48ANJuu1CURtRdHKUBY5Lm1tHV+sD4g==}
tinyrainbow@3.1.1:
resolution: {integrity: sha512-yau8yJdTt989Mm0Bd/236QnzEiPf2xLLTqUZRUJOo/3CB078LSwzei343DgtJVmfJKJE3TMINY1u42SQsP6mXw==}
engines: {node: '>=14.0.0'}
@@ -4072,6 +4156,47 @@ snapshots:
'@jridgewell/resolve-uri': 3.1.2
'@jridgewell/sourcemap-codec': 1.5.5
'@mapbox/jsonlint-lines-primitives@2.0.3': {}
'@mapbox/point-geometry@1.1.0': {}
'@mapbox/tiny-sdf@2.3.0': {}
'@mapbox/unitbezier@0.0.1': {}
'@mapbox/unitbezier@1.0.0': {}
'@mapbox/vector-tile@2.0.5':
dependencies:
'@mapbox/point-geometry': 1.1.0
'@types/geojson': 7946.0.16
pbf: 4.0.2
'@mapbox/whoots-js@3.1.0': {}
'@maplibre/geojson-vt@6.1.2':
dependencies:
kdbush: 4.1.0
'@maplibre/maplibre-gl-style-spec@24.10.0':
dependencies:
'@mapbox/jsonlint-lines-primitives': 2.0.3
'@mapbox/unitbezier': 1.0.0
json-stringify-pretty-compact: 4.0.0
minimist: 1.2.8
quickselect: 3.0.0
tinyqueue: 3.0.0
'@maplibre/mlt@1.3.0':
dependencies:
'@mapbox/point-geometry': 1.1.0
'@maplibre/vt-pbf@4.3.2':
dependencies:
'@mapbox/point-geometry': 1.1.0
'@types/geojson': 7946.0.16
pbf: 5.1.2
'@mediapipe/tasks-vision@0.10.35': {}
'@napi-rs/wasm-runtime@1.1.6(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)':
@@ -5345,6 +5470,8 @@ snapshots:
'@types/estree@1.0.9': {}
'@types/geojson@7946.0.16': {}
'@types/hast@3.0.4':
dependencies:
'@types/unist': 3.0.3
@@ -5653,6 +5780,8 @@ snapshots:
es-errors: 1.3.0
gopd: 1.2.0
earcut@3.2.4: {}
electron-to-chromium@1.5.361: {}
embla-carousel-react@8.6.0(react@19.2.8):
@@ -5768,6 +5897,8 @@ snapshots:
gitdiff-parser@0.3.1: {}
gl-matrix@3.4.4: {}
gopd@1.2.0: {}
graceful-fs@4.2.11: {}
@@ -5941,12 +6072,16 @@ snapshots:
jsesc@3.1.0: {}
json-stringify-pretty-compact@4.0.0: {}
json5@2.2.3: {}
just-debounce-it@1.1.0: {}
just-once@1.1.0: {}
kdbush@4.1.0: {}
lightningcss-android-arm64@1.32.0:
optional: true
@@ -6030,6 +6165,28 @@ snapshots:
dependencies:
'@jridgewell/sourcemap-codec': 1.5.5
maplibre-gl@5.24.0:
dependencies:
'@mapbox/jsonlint-lines-primitives': 2.0.3
'@mapbox/point-geometry': 1.1.0
'@mapbox/tiny-sdf': 2.3.0
'@mapbox/unitbezier': 0.0.1
'@mapbox/vector-tile': 2.0.5
'@mapbox/whoots-js': 3.1.0
'@maplibre/geojson-vt': 6.1.2
'@maplibre/maplibre-gl-style-spec': 24.10.0
'@maplibre/mlt': 1.3.0
'@maplibre/vt-pbf': 4.3.2
'@types/geojson': 7946.0.16
earcut: 3.2.4
gl-matrix: 3.4.4
kdbush: 4.1.0
murmurhash-js: 1.0.0
pbf: 4.0.2
potpack: 2.1.0
quickselect: 3.0.0
tinyqueue: 3.0.0
markdown-it-task-lists@2.1.1: {}
markdown-it@14.1.1:
@@ -6424,6 +6581,8 @@ snapshots:
ms@2.1.3: {}
murmurhash-js@1.0.0: {}
nanoid@3.3.16: {}
nanoid@3.3.17: {}
@@ -6490,6 +6649,14 @@ snapshots:
pathe@2.0.3: {}
pbf@4.0.2:
dependencies:
resolve-protobuf-schema: 2.1.0
pbf@5.1.2:
dependencies:
resolve-protobuf-schema: 2.1.0
picocolors@1.1.1: {}
picomatch@4.0.5: {}
@@ -6527,6 +6694,8 @@ snapshots:
picocolors: 1.1.1
source-map-js: 1.2.1
potpack@2.1.0: {}
prettier@3.8.3: {}
pretty-format@27.5.1:
@@ -6614,6 +6783,8 @@ snapshots:
prosemirror-state: 1.4.4
prosemirror-transform: 1.12.0
protocol-buffers-schema@3.6.1: {}
punycode.js@2.3.1: {}
punycode@2.3.1: {}
@@ -6628,6 +6799,8 @@ snapshots:
pngjs: 5.0.0
yargs: 15.4.1
quickselect@3.0.0: {}
react-day-picker@10.0.1(@types/react@19.2.18)(react@19.2.8):
dependencies:
'@date-fns/tz': 1.5.0
@@ -6771,6 +6944,10 @@ snapshots:
require-main-filename@2.0.0: {}
resolve-protobuf-schema@2.1.0:
dependencies:
protocol-buffers-schema: 3.6.1
rolldown@1.0.3:
dependencies:
'@oxc-project/types': 0.133.0
@@ -6913,6 +7090,8 @@ snapshots:
fdir: 6.5.0(picomatch@4.0.7)
picomatch: 4.0.7
tinyqueue@3.0.0: {}
tinyrainbow@3.1.1: {}
tiptap-markdown@0.9.0(@tiptap/core@3.22.5(@tiptap/pm@3.22.5)):