From b93504ae494c56242487f0b25868071e8476fb66 Mon Sep 17 00:00:00 2001 From: Wintermute Date: Fri, 9 Oct 2026 13:14:59 -0400 Subject: [PATCH] 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 --- desktop/package.json | 1 + desktop/src/shared/ui/markdown/CodeBlock.tsx | 10 + desktop/src/shared/ui/markdown/MapBlock.tsx | 102 ++++++++++ .../src/shared/ui/markdown/mapSpec.test.mjs | 69 +++++++ desktop/src/shared/ui/markdown/mapSpec.ts | 135 +++++++++++++ pnpm-lock.yaml | 179 ++++++++++++++++++ 6 files changed, 496 insertions(+) create mode 100644 desktop/src/shared/ui/markdown/MapBlock.tsx create mode 100644 desktop/src/shared/ui/markdown/mapSpec.test.mjs create mode 100644 desktop/src/shared/ui/markdown/mapSpec.ts diff --git a/desktop/package.json b/desktop/package.json index 964c644168..ddbf09f81b 100644 --- a/desktop/package.json +++ b/desktop/package.json @@ -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", diff --git a/desktop/src/shared/ui/markdown/CodeBlock.tsx b/desktop/src/shared/ui/markdown/CodeBlock.tsx index 9954b03aa6..e18cc05b8d 100644 --- a/desktop/src/shared/ui/markdown/CodeBlock.tsx +++ b/desktop/src/shared/ui/markdown/CodeBlock.tsx @@ -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 { if (shikiHighlighter) return Promise.resolve(); @@ -96,6 +98,14 @@ export function MarkdownCodeBlock({ [code], ); + if (language === "map") { + return ( + + + + ); + } + return (
 ({
+        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(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 (
+    
+ ); +} + +export default function MapBlock({ code }: { code: string }) { + const result = React.useMemo(() => parseMapSpec(code), [code]); + if (!result.ok) { + return ( +

+ {result.error} +

+ ); + } + return ; +} diff --git a/desktop/src/shared/ui/markdown/mapSpec.test.mjs b/desktop/src/shared/ui/markdown/mapSpec.test.mjs new file mode 100644 index 0000000000..7336047741 --- /dev/null +++ b/desktop/src/shared/ui/markdown/mapSpec.test.mjs @@ -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.", + ); +}); diff --git a/desktop/src/shared/ui/markdown/mapSpec.ts b/desktop/src/shared/ui/markdown/mapSpec.ts new file mode 100644 index 0000000000..86ffcee3fd --- /dev/null +++ b/desktop/src/shared/ui/markdown/mapSpec.ts @@ -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; + + const markers: MapMarker[] = []; + for (const [index, item] of (Array.isArray(input.markers) + ? input.markers + : [] + ).entries()) { + const marker = (item ?? {}) as Record; + 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; + 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), + ]; +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3847cdc5e7..c597f3c0aa 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -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)):