feat(core): add readiness adapters for map and visualization libraries (#1548)

Add readiness-only runtime adapters for Mapbox GL JS, Leaflet, Google
Maps, MapLibre GL JS, and D3. Each adapter gates `__renderReady` until
the library's async initialization completes, preventing the renderer
from capturing blank or half-loaded frames.

Built on the `getReadyPromise` adapter contract from #1543. A shared
`createReadinessAdapter()` helper in `_readiness.ts` owns the
settled-tracking WeakSet, promise-identity stability, and
`Promise.allSettled` gate — each adapter provides only its type, window
global name, and `waitFor` callback.

Readiness signals per library:
- Mapbox / MapLibre: `map.loaded()` + `map.on('load', ...)`
- Leaflet: `map.whenReady(cb)`
- Google Maps: `map.addListener('tilesloaded', cb)` with handle cleanup
- D3: `transition.end()` promise

50 unit tests across 5 test files covering happy path, no-instances,
stable promise identity, post-settle drain, loaded-before-subscribe
race, and listener cleanup. 5 producer regression tests with
Docker-generated baselines for end-to-end render verification.
This commit is contained in:
Miguel Ángel
2026-06-18 01:21:27 -04:00
committed by GitHub
parent bb5f5f8c5c
commit 75d92b55e0
33 changed files with 2294 additions and 0 deletions
@@ -0,0 +1,36 @@
import type { RuntimeDeterministicAdapter } from "../types";
export function createReadinessAdapter<T extends object>(opts: {
name: string;
getInstances: () => T[];
waitFor: (instance: T) => PromiseLike<void>;
}): RuntimeDeterministicAdapter {
let pendingPromise: PromiseLike<void> | null = null;
const settled = new WeakSet<T>();
return {
name: opts.name,
discover: () => {},
seek: () => {},
pause: () => {},
play: () => {},
revert: () => {},
getReadyPromise: () => {
const instances = opts.getInstances();
if (instances.length === 0) return null;
const unsettled = instances.filter((i) => !settled.has(i));
if (unsettled.length === 0) return null;
if (pendingPromise) return pendingPromise;
pendingPromise = Promise.allSettled(
unsettled.map((i) =>
opts.waitFor(i).then(() => {
settled.add(i);
}),
),
).then(() => {
pendingPromise = null;
});
return pendingPromise;
},
};
}
@@ -0,0 +1,100 @@
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
import { createD3Adapter } from "./d3";
type D3TransitionLike = {
end: () => PromiseLike<void>;
};
const d3Window = window as Window & { __hfD3?: D3TransitionLike[] };
function createMockTransition(opts?: { resolved?: boolean }): D3TransitionLike {
let resolver: (() => void) | null = null;
return {
end: vi.fn(
() =>
new Promise<void>((resolve) => {
if (opts?.resolved) {
resolve();
} else {
resolver = resolve;
}
}),
),
_resolve() {
resolver?.();
},
} as D3TransitionLike & { _resolve: () => void };
}
describe("d3 adapter", () => {
beforeEach(() => {
delete d3Window.__hfD3;
});
afterEach(() => {
delete d3Window.__hfD3;
});
it("has correct name", () => {
expect(createD3Adapter().name).toBe("d3");
});
describe("getReadyPromise", () => {
it("returns null when no transitions registered", () => {
const adapter = createD3Adapter();
expect(adapter.getReadyPromise!()).toBeNull();
});
it("returns null when __hfD3 is empty", () => {
d3Window.__hfD3 = [];
const adapter = createD3Adapter();
expect(adapter.getReadyPromise!()).toBeNull();
});
it("resolves when transition ends", async () => {
const t = createMockTransition() as D3TransitionLike & { _resolve: () => void };
d3Window.__hfD3 = [t];
const adapter = createD3Adapter();
const promise = adapter.getReadyPromise!();
expect(promise).not.toBeNull();
t._resolve();
await promise;
});
it("resolves immediately for already-resolved transition", async () => {
const t = createMockTransition({ resolved: true });
d3Window.__hfD3 = [t];
const adapter = createD3Adapter();
const promise = adapter.getReadyPromise!();
expect(promise).not.toBeNull();
await promise;
});
it("returns same promise on repeated calls (stable identity)", () => {
const t = createMockTransition();
d3Window.__hfD3 = [t];
const adapter = createD3Adapter();
const p1 = adapter.getReadyPromise!();
const p2 = adapter.getReadyPromise!();
expect(p1).toBe(p2);
});
it("returns null after all transitions have settled", async () => {
const t = createMockTransition({ resolved: true });
d3Window.__hfD3 = [t];
const adapter = createD3Adapter();
await adapter.getReadyPromise!();
expect(adapter.getReadyPromise!()).toBeNull();
});
});
it("discover is a no-op", () => {
const adapter = createD3Adapter();
expect(() => adapter.discover()).not.toThrow();
});
it("seek is a no-op", () => {
const adapter = createD3Adapter();
expect(() => adapter.seek({ time: 1 })).not.toThrow();
});
});
+17
View File
@@ -0,0 +1,17 @@
import { createReadinessAdapter } from "./_readiness";
type D3TransitionLike = {
end: () => PromiseLike<void>;
};
export function createD3Adapter() {
return createReadinessAdapter<D3TransitionLike>({
name: "d3",
getInstances: () => {
if (typeof window === "undefined") return [];
const arr = (window as { __hfD3?: D3TransitionLike[] }).__hfD3;
return Array.isArray(arr) ? arr : [];
},
waitFor: (t) => t.end(),
});
}
@@ -0,0 +1,111 @@
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
import { createGoogleMapsAdapter } from "./google-maps";
type GoogleMapLike = {
addListener: (event: string, cb: () => void) => { remove: () => void };
};
const mapWindow = window as Window & { __hfGoogleMaps?: GoogleMapLike[] };
function createMockMap(): GoogleMapLike & { _fire: (e: string) => void } {
const listeners: Record<string, { cb: () => void; handle: { remove: () => void } }[]> = {};
return {
addListener: vi.fn((event: string, cb: () => void) => {
const entry = { cb, handle: { remove: vi.fn() } };
(listeners[event] ??= []).push(entry);
return entry.handle;
}),
_fire(event: string) {
for (const entry of listeners[event] ?? []) entry.cb();
},
};
}
describe("google-maps adapter", () => {
beforeEach(() => {
delete mapWindow.__hfGoogleMaps;
});
afterEach(() => {
delete mapWindow.__hfGoogleMaps;
});
it("has correct name", () => {
expect(createGoogleMapsAdapter().name).toBe("google-maps");
});
describe("getReadyPromise", () => {
it("returns null when no maps registered", () => {
const adapter = createGoogleMapsAdapter();
expect(adapter.getReadyPromise!()).toBeNull();
});
it("returns null when __hfGoogleMaps is empty", () => {
mapWindow.__hfGoogleMaps = [];
const adapter = createGoogleMapsAdapter();
expect(adapter.getReadyPromise!()).toBeNull();
});
it("resolves when map fires tilesloaded", async () => {
const map = createMockMap();
mapWindow.__hfGoogleMaps = [map];
const adapter = createGoogleMapsAdapter();
const promise = adapter.getReadyPromise!();
expect(promise).not.toBeNull();
map._fire("tilesloaded");
await promise;
});
it("returns same promise on repeated calls (stable identity)", () => {
const map = createMockMap();
mapWindow.__hfGoogleMaps = [map];
const adapter = createGoogleMapsAdapter();
const p1 = adapter.getReadyPromise!();
const p2 = adapter.getReadyPromise!();
expect(p1).toBe(p2);
});
it("returns null after all maps have settled", async () => {
const map = createMockMap();
mapWindow.__hfGoogleMaps = [map];
const adapter = createGoogleMapsAdapter();
const promise = adapter.getReadyPromise!();
map._fire("tilesloaded");
await promise;
expect(adapter.getReadyPromise!()).toBeNull();
});
it("removes listener after first tilesloaded fire", async () => {
const map = createMockMap();
mapWindow.__hfGoogleMaps = [map];
const adapter = createGoogleMapsAdapter();
const promise = adapter.getReadyPromise!();
map._fire("tilesloaded");
await promise;
const handle = (map.addListener as ReturnType<typeof vi.fn>).mock.results[0].value;
expect(handle.remove).toHaveBeenCalled();
});
it("handles multiple maps", async () => {
const map1 = createMockMap();
const map2 = createMockMap();
mapWindow.__hfGoogleMaps = [map1, map2];
const adapter = createGoogleMapsAdapter();
const promise = adapter.getReadyPromise!();
map1._fire("tilesloaded");
map2._fire("tilesloaded");
await promise;
expect(adapter.getReadyPromise!()).toBeNull();
});
});
it("discover is a no-op", () => {
const adapter = createGoogleMapsAdapter();
expect(() => adapter.discover()).not.toThrow();
});
it("seek is a no-op", () => {
const adapter = createGoogleMapsAdapter();
expect(() => adapter.seek({ time: 1 })).not.toThrow();
});
});
@@ -0,0 +1,23 @@
import { createReadinessAdapter } from "./_readiness";
type GoogleMapLike = {
addListener: (event: string, cb: () => void) => { remove: () => void };
};
export function createGoogleMapsAdapter() {
return createReadinessAdapter<GoogleMapLike>({
name: "google-maps",
getInstances: () => {
if (typeof window === "undefined") return [];
const arr = (window as { __hfGoogleMaps?: GoogleMapLike[] }).__hfGoogleMaps;
return Array.isArray(arr) ? arr : [];
},
waitFor: (m) =>
new Promise<void>((resolve) => {
const handle = m.addListener("tilesloaded", () => {
handle.remove();
resolve();
});
}),
});
}
@@ -0,0 +1,93 @@
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
import { createLeafletAdapter } from "./leaflet";
type LeafletMapLike = {
whenReady: (cb: () => void) => void;
};
const mapWindow = window as Window & { __hfLeaflet?: LeafletMapLike[] };
function createMockMap(opts?: { ready?: boolean }): LeafletMapLike {
return {
whenReady: vi.fn((cb: () => void) => {
if (opts?.ready ?? false) cb();
}),
_fireReady() {
const calls = (this.whenReady as ReturnType<typeof vi.fn>).mock.calls;
for (const [cb] of calls) cb();
},
} as LeafletMapLike & { _fireReady: () => void };
}
describe("leaflet adapter", () => {
beforeEach(() => {
delete mapWindow.__hfLeaflet;
});
afterEach(() => {
delete mapWindow.__hfLeaflet;
});
it("has correct name", () => {
expect(createLeafletAdapter().name).toBe("leaflet");
});
describe("getReadyPromise", () => {
it("returns null when no maps registered", () => {
const adapter = createLeafletAdapter();
expect(adapter.getReadyPromise!()).toBeNull();
});
it("returns null when __hfLeaflet is empty", () => {
mapWindow.__hfLeaflet = [];
const adapter = createLeafletAdapter();
expect(adapter.getReadyPromise!()).toBeNull();
});
it("resolves when map fires whenReady callback", async () => {
const map = createMockMap() as LeafletMapLike & { _fireReady: () => void };
mapWindow.__hfLeaflet = [map];
const adapter = createLeafletAdapter();
const promise = adapter.getReadyPromise!();
expect(promise).not.toBeNull();
map._fireReady();
await promise;
});
it("resolves immediately for already-ready map", async () => {
const map = createMockMap({ ready: true });
mapWindow.__hfLeaflet = [map];
const adapter = createLeafletAdapter();
const promise = adapter.getReadyPromise!();
expect(promise).not.toBeNull();
await promise;
});
it("returns same promise on repeated calls (stable identity)", () => {
const map = createMockMap();
mapWindow.__hfLeaflet = [map];
const adapter = createLeafletAdapter();
const p1 = adapter.getReadyPromise!();
const p2 = adapter.getReadyPromise!();
expect(p1).toBe(p2);
});
it("returns null after all maps have settled", async () => {
const map = createMockMap({ ready: true });
mapWindow.__hfLeaflet = [map];
const adapter = createLeafletAdapter();
await adapter.getReadyPromise!();
expect(adapter.getReadyPromise!()).toBeNull();
});
});
it("discover is a no-op", () => {
const adapter = createLeafletAdapter();
expect(() => adapter.discover()).not.toThrow();
});
it("seek is a no-op", () => {
const adapter = createLeafletAdapter();
expect(() => adapter.seek({ time: 1 })).not.toThrow();
});
});
@@ -0,0 +1,17 @@
import { createReadinessAdapter } from "./_readiness";
type LeafletMapLike = {
whenReady: (cb: () => void) => void;
};
export function createLeafletAdapter() {
return createReadinessAdapter<LeafletMapLike>({
name: "leaflet",
getInstances: () => {
if (typeof window === "undefined") return [];
const arr = (window as { __hfLeaflet?: LeafletMapLike[] }).__hfLeaflet;
return Array.isArray(arr) ? arr : [];
},
waitFor: (m) => new Promise<void>((resolve) => m.whenReady(resolve)),
});
}
@@ -0,0 +1,120 @@
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
import { createMapboxAdapter } from "./mapbox";
type MapboxMapLike = {
loaded: () => boolean;
on: (event: string, cb: () => void) => void;
};
const mapWindow = window as Window & { __hfMapbox?: MapboxMapLike[] };
function createMockMap(opts?: { loaded?: boolean }): MapboxMapLike {
const listeners: Record<string, (() => void)[]> = {};
return {
loaded: vi.fn(() => opts?.loaded ?? false),
on: vi.fn((event: string, cb: () => void) => {
(listeners[event] ??= []).push(cb);
}),
_fire(event: string) {
for (const cb of listeners[event] ?? []) cb();
},
} as MapboxMapLike & { _fire: (e: string) => void };
}
describe("mapbox adapter", () => {
beforeEach(() => {
delete mapWindow.__hfMapbox;
});
afterEach(() => {
delete mapWindow.__hfMapbox;
});
it("has correct name", () => {
expect(createMapboxAdapter().name).toBe("mapbox");
});
describe("getReadyPromise", () => {
it("returns null when no maps registered", () => {
const adapter = createMapboxAdapter();
expect(adapter.getReadyPromise!()).toBeNull();
});
it("returns null when __hfMapbox is empty", () => {
mapWindow.__hfMapbox = [];
const adapter = createMapboxAdapter();
expect(adapter.getReadyPromise!()).toBeNull();
});
it("resolves when map fires load event", async () => {
const map = createMockMap() as MapboxMapLike & { _fire: (e: string) => void };
mapWindow.__hfMapbox = [map];
const adapter = createMapboxAdapter();
const promise = adapter.getReadyPromise!();
expect(promise).not.toBeNull();
map._fire("load");
await promise;
});
it("resolves immediately for already-loaded map", async () => {
const map = createMockMap({ loaded: true });
mapWindow.__hfMapbox = [map];
const adapter = createMapboxAdapter();
const promise = adapter.getReadyPromise!();
expect(promise).not.toBeNull();
await promise;
});
it("returns same promise on repeated calls (stable identity)", () => {
const map = createMockMap();
mapWindow.__hfMapbox = [map];
const adapter = createMapboxAdapter();
const p1 = adapter.getReadyPromise!();
const p2 = adapter.getReadyPromise!();
expect(p1).toBe(p2);
});
it("returns null after all maps have settled", async () => {
const map = createMockMap({ loaded: true });
mapWindow.__hfMapbox = [map];
const adapter = createMapboxAdapter();
await adapter.getReadyPromise!();
expect(adapter.getReadyPromise!()).toBeNull();
});
it("handles mix of loaded and unloaded maps", async () => {
const loaded = createMockMap({ loaded: true });
const unloaded = createMockMap() as MapboxMapLike & { _fire: (e: string) => void };
mapWindow.__hfMapbox = [loaded, unloaded];
const adapter = createMapboxAdapter();
const promise = adapter.getReadyPromise!();
expect(promise).not.toBeNull();
unloaded._fire("load");
await promise;
expect(adapter.getReadyPromise!()).toBeNull();
});
it("resolves via loaded() check when map finishes loading before subscribe", async () => {
const racyMap = {
loaded: vi.fn(() => true),
on: vi.fn(),
};
mapWindow.__hfMapbox = [racyMap];
const adapter = createMapboxAdapter();
const promise = adapter.getReadyPromise!();
expect(promise).not.toBeNull();
await promise;
expect(racyMap.on).not.toHaveBeenCalled();
});
});
it("discover is a no-op", () => {
const adapter = createMapboxAdapter();
expect(() => adapter.discover()).not.toThrow();
});
it("seek is a no-op", () => {
const adapter = createMapboxAdapter();
expect(() => adapter.seek({ time: 1 })).not.toThrow();
});
});
@@ -0,0 +1,26 @@
import { createReadinessAdapter } from "./_readiness";
type MapboxMapLike = {
loaded: () => boolean;
on: (event: string, cb: () => void) => void;
};
// 'load' = style + sources ready (not tile-level 'idle'); sufficient for render-ready gate
export function createMapboxAdapter() {
return createReadinessAdapter<MapboxMapLike>({
name: "mapbox",
getInstances: () => {
if (typeof window === "undefined") return [];
const arr = (window as { __hfMapbox?: MapboxMapLike[] }).__hfMapbox;
return Array.isArray(arr) ? arr : [];
},
waitFor: (m) =>
new Promise<void>((resolve) => {
if (m.loaded()) {
resolve();
return;
}
m.on("load", resolve);
}),
});
}
@@ -0,0 +1,122 @@
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
import { createMaplibreAdapter } from "./maplibre";
type MaplibreMapLike = {
loaded: () => boolean;
on: (event: string, cb: () => void) => void;
};
const mapWindow = window as Window & { __hfMaplibre?: MaplibreMapLike[] };
function createMockMap(opts?: {
loaded?: boolean;
}): MaplibreMapLike & { _fire: (e: string) => void } {
const listeners: Record<string, (() => void)[]> = {};
return {
loaded: vi.fn(() => opts?.loaded ?? false),
on: vi.fn((event: string, cb: () => void) => {
(listeners[event] ??= []).push(cb);
}),
_fire(event: string) {
for (const cb of listeners[event] ?? []) cb();
},
};
}
describe("maplibre adapter", () => {
beforeEach(() => {
delete mapWindow.__hfMaplibre;
});
afterEach(() => {
delete mapWindow.__hfMaplibre;
});
it("has correct name", () => {
expect(createMaplibreAdapter().name).toBe("maplibre");
});
describe("getReadyPromise", () => {
it("returns null when no maps registered", () => {
const adapter = createMaplibreAdapter();
expect(adapter.getReadyPromise!()).toBeNull();
});
it("returns null when __hfMaplibre is empty", () => {
mapWindow.__hfMaplibre = [];
const adapter = createMaplibreAdapter();
expect(adapter.getReadyPromise!()).toBeNull();
});
it("resolves when map fires load event", async () => {
const map = createMockMap();
mapWindow.__hfMaplibre = [map];
const adapter = createMaplibreAdapter();
const promise = adapter.getReadyPromise!();
expect(promise).not.toBeNull();
map._fire("load");
await promise;
});
it("resolves immediately for already-loaded map", async () => {
const map = createMockMap({ loaded: true });
mapWindow.__hfMaplibre = [map];
const adapter = createMaplibreAdapter();
const promise = adapter.getReadyPromise!();
expect(promise).not.toBeNull();
await promise;
});
it("returns same promise on repeated calls (stable identity)", () => {
const map = createMockMap();
mapWindow.__hfMaplibre = [map];
const adapter = createMaplibreAdapter();
const p1 = adapter.getReadyPromise!();
const p2 = adapter.getReadyPromise!();
expect(p1).toBe(p2);
});
it("returns null after all maps have settled", async () => {
const map = createMockMap({ loaded: true });
mapWindow.__hfMaplibre = [map];
const adapter = createMaplibreAdapter();
await adapter.getReadyPromise!();
expect(adapter.getReadyPromise!()).toBeNull();
});
it("handles mix of loaded and unloaded maps", async () => {
const loaded = createMockMap({ loaded: true });
const unloaded = createMockMap();
mapWindow.__hfMaplibre = [loaded, unloaded];
const adapter = createMaplibreAdapter();
const promise = adapter.getReadyPromise!();
expect(promise).not.toBeNull();
unloaded._fire("load");
await promise;
expect(adapter.getReadyPromise!()).toBeNull();
});
it("resolves via loaded() check when map finishes loading before subscribe", async () => {
const racyMap = {
loaded: vi.fn(() => true),
on: vi.fn(),
};
mapWindow.__hfMaplibre = [racyMap];
const adapter = createMaplibreAdapter();
const promise = adapter.getReadyPromise!();
expect(promise).not.toBeNull();
await promise;
expect(racyMap.on).not.toHaveBeenCalled();
});
});
it("discover is a no-op", () => {
const adapter = createMaplibreAdapter();
expect(() => adapter.discover()).not.toThrow();
});
it("seek is a no-op", () => {
const adapter = createMaplibreAdapter();
expect(() => adapter.seek({ time: 1 })).not.toThrow();
});
});
@@ -0,0 +1,26 @@
import { createReadinessAdapter } from "./_readiness";
type MaplibreMapLike = {
loaded: () => boolean;
on: (event: string, cb: () => void) => void;
};
// 'load' = style + sources ready; same contract as Mapbox GL JS
export function createMaplibreAdapter() {
return createReadinessAdapter<MaplibreMapLike>({
name: "maplibre",
getInstances: () => {
if (typeof window === "undefined") return [];
const arr = (window as { __hfMaplibre?: MaplibreMapLike[] }).__hfMaplibre;
return Array.isArray(arr) ? arr : [];
},
waitFor: (m) =>
new Promise<void>((resolve) => {
if (m.loaded()) {
resolve();
return;
}
m.on("load", resolve);
}),
});
}
+10
View File
@@ -6,6 +6,11 @@ import { createGsapAdapter } from "./adapters/gsap";
import { createAnimeJsAdapter } from "./adapters/animejs";
import { createLottieAdapter } from "./adapters/lottie";
import { createThreeAdapter } from "./adapters/three";
import { createMapboxAdapter } from "./adapters/mapbox";
import { createLeafletAdapter } from "./adapters/leaflet";
import { createGoogleMapsAdapter } from "./adapters/google-maps";
import { createMaplibreAdapter } from "./adapters/maplibre";
import { createD3Adapter } from "./adapters/d3";
import { createTypegpuAdapter } from "./adapters/typegpu";
import {
patchVideoTextureCompat,
@@ -1913,6 +1918,11 @@ export function initSandboxRuntimeModular(): void {
createAnimeJsAdapter(),
createLottieAdapter(),
createThreeAdapter(),
createMapboxAdapter(),
createLeafletAdapter(),
createGoogleMapsAdapter(),
createMaplibreAdapter(),
createD3Adapter(),
createTypegpuAdapter(),
createGsapAdapter({ getTimeline: () => state.capturedTimeline }),
] as RuntimeDeterministicAdapter[];
+30
View File
@@ -102,6 +102,36 @@ declare global {
* window.__hfLottie.push(anim);
*/
__hfLottie?: unknown[];
/**
* Mapbox GL JS map instances. Push your map here after creating it:
* window.__hfMapbox = window.__hfMapbox || [];
* window.__hfMapbox.push(map);
*/
__hfMapbox?: unknown[];
/**
* Leaflet map instances. Push your map here after creating it:
* window.__hfLeaflet = window.__hfLeaflet || [];
* window.__hfLeaflet.push(map);
*/
__hfLeaflet?: unknown[];
/**
* Google Maps instances. Push your map here after creating it:
* window.__hfGoogleMaps = window.__hfGoogleMaps || [];
* window.__hfGoogleMaps.push(map);
*/
__hfGoogleMaps?: unknown[];
/**
* MapLibre GL JS map instances. Push your map here after creating it:
* window.__hfMaplibre = window.__hfMaplibre || [];
* window.__hfMaplibre.push(map);
*/
__hfMaplibre?: unknown[];
/**
* D3 transition instances. Push your transition here after creating it:
* window.__hfD3 = window.__hfD3 || [];
* window.__hfD3.push(transition);
*/
__hfD3?: unknown[];
/**
* Render-time variable overrides injected by the engine when the user
* passes `hyperframes render --variables '<json>'`. Read indirectly via