Skip to content

feat(v4): port the resource loading helpers - #828

Merged
titouanmathis merged 2 commits into
mainfrom
feature/v4-load-helpers
Aug 16, 2026
Merged

titouanmathis merged 2 commits into
mainfrom
feature/v4-load-helpers

Conversation

@titouanmathis

Copy link
Copy Markdown
Contributor

Ports v3's resource-loading helpers to v4, redesigned. One module, packages/v4/src/utils/load.ts, three functions:

loadImage(src: string): Promise<HTMLImageElement>
loadScript(src: string, attributes?: Record<string, string>): Promise<HTMLScriptElement>
loadLink(href: string, attributes: { rel: string } & Record<string, string>): Promise<HTMLLinkElement>

The six v3 defects this fixes

  1. A rejection that carried no message. v3 rejected with a plain { event, element } object, so catch (error) { error.message } was undefined and error reporters dropped the failure entirely. Every rejection is now new Error(`Failed to load "${src}".`, { cause: event }) — a real Error, naming the source, with the event kept as the cause.
  2. An image was ready before it was ready. v3 resolved on the load event, which fires before the bitmap is decoded, so assigning the URL to a visible <img> decoded on the main thread and janked the next frame. loadImage awaits decode() instead.
  3. loadScript executed a third-party embed once per call. Nothing deduplicated: two components asking for the same maps/analytics/player SDK appended two <script> elements and ran it twice. loadScript and loadLink now deduplicate on the resolved URL (new URL(src, location.href).href) and hand back the very same promise. The maps live in a shared runtime slot (getSharedRuntimeSlot('load', …), the pattern storage/providers.ts and utils/focus.ts use), so two evaluated copies of the package cannot double-execute an embed either.
  4. loadLink could never settle. loadLink(src) set only href; a <link> with no rel is inert and fires no event, so the promise hung forever. rel is now required by the type.
  5. A <link> in a browser that ignores its rel leaked the same way. Safari ignores prefetch entirely — no load event, ever. loadLink asks relList.supports(rel) first (see below).
  6. loadImage had a pointless appendTo, loadScript a dangerous one. v3's one generic took { appendTo } for all six element types: an image never needs the document, and a script or a link put anywhere but <head> is either inert or a bug. The option is gone; loadImage never touches the document, loadScript/loadLink always append to <head>.

decode(), measured in real Chromium

decode() cannot be trusted alone as a failure signal, which the specs prove rather than assume. Removing the events fallback makes three specs fail, including the plain "a broken image rejects" one: for data:image/gif;base64,bm90LWEtZ2lm, Chromium's decode() rejects with a raw EncodingError DOMException, which would have escaped as the caller's error — the same shape Safari produces on cross-origin images that load perfectly. The two are indistinguishable from the rejection.

So the load/error events are the authority and decode() is only ever the fast path:

image.src = src;

if (typeof image.decode === 'function') {
  try {
    await image.decode();
    return image;
  } catch {
    // Not proof of failure. Fall through to the events.
  }
}

await settled; // resolves on `load`, rejects with the Error on `error`
return image;

A rejected decode() on an image that did load falls through to an already-resolved settled; a rejected decode() on a genuine failure falls through to the real Error. The same path covers a browser with no decode() at all. Three specs cover the three cases, plus one that wraps the real decode() to assert the promise does not resolve before it does.

An unsupported rel resolves at once

document.createElement('link').relList.supports(rel) answers whether the browser will act on a rel. When it will not, the element is still appended and the promise resolves immediately with it — a hint is best-effort, and waiting for an event that is never coming is a leak, not caution. This is the owner's leaning, and it is documented in the JSDoc. The spec asserts it; with the check removed, that spec fails on a 15-second timeout, which is exactly the leak.

Multi-token rel values are supported (supports() throws on an empty token, so tokens are split and any supported one counts).

The four @studiometa/ui loadImage call sites

All four await loadImage(src) then assign the URL, and use nothing from the return value. They translate with no change but the import, and each one silently gains the decode:

File v3 v4
Figure/AbstractFigure.ts:86 try { await loadImage(src) } catch { … } identical — and this.src = src now paints without a decode on the main thread
Figure/AbstractFigureDynamic.ts:67 try { await loadImage(original) } catch { … } identical
FigureVideo/FigureVideo.ts:70 loadImage(poster).then(() => …).catch(() => …) identical
FigureVideo/FigureVideoTwicpics.ts:124 loadImage(twicPoster).then(() => …).catch(() => …) identical

The one behaviour change they see: their catch blocks now receive an Error with a message instead of { event, element }. All four discard the argument and log their own message, so nothing breaks — and anything that did read error.message starts working.

Prefetch/AbstractPrefetch.ts is the other consumer this unblocks: it hand-rolls document.createElement('link'), rel = 'prefetch', a static Set of prefetched URLs and a load listener. That is loadLink(url.href, { rel: 'prefetch' }) — with the dedup shared across evaluated copies rather than per class, and no hang in Safari.

Deliberately dropped

loadIframe, loadEmbed, loadTrack and the generic loadElement are not ported. Zero consumers across @studiometa/ui, and an <iframe> you want in the page you write in markup rather than build in JavaScript. The generic is what forced every helper to share one wrong set of rules in the first place.

One judgement call worth flagging

A failed load is forgotten, so a later call retries instead of replaying a stored rejection — a network blip must not make a URL unloadable for the life of the page. The failed element is removed from <head> too. The eviction happens inside the error listener, before reject(), so no .catch() is attached internally and an unhandled rejection still surfaces to the console. Spec: rejects with an Error naming the source, and allows a retry.

Verification

npm run lint, npm run lint:types, npm run test:v4 (991 tests, 73 files, all passing) and packages/v4 → npm run check:package are green on each of the two commits. The 17 new specs run in real Chromium under vitest browser mode and touch no network: data URIs for the successes, a same-origin 404 for the failures.

Three mutations were run against the implementation to confirm the specs bite: dropping the decode() fallback fails 3, dropping rel from the dedup key fails 1, and always waiting for a load event fails 1 on a timeout.

🤖 Generated with Claude Code

https://claude.ai/code/session_011nNdFD3aQhzfdm3EsCSbS9

@codecov

codecov Bot commented Aug 16, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 97.16%. Comparing base (a00ca20) to head (b16a212).

Additional details and impacted files
@@           Coverage Diff           @@
##             main     #828   +/-   ##
=======================================
  Coverage   97.16%   97.16%           
=======================================
  Files         170      170           
  Lines        4133     4133           
  Branches     1152     1152           
=======================================
  Hits         4016     4016           
  Misses        106      106           
  Partials       11       11           
Flag Coverage Δ
eslint-plugin-js-toolkit 93.79% <ø> (ø)
js-toolkit 97.92% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@github-actions

github-actions Bot commented Aug 16, 2026

Copy link
Copy Markdown

Code Review

Risk: Low — The change adds the three resource-loading helpers and their barrel and subpath exports without identifiable blocking defects; it is safe to merge.

Ports redesigned image, script, and link loading utilities to v4. Images remain detached and use decode with event fallback, while scripts and links load in the document head with shared URL-based deduplication and structured errors.


Review usage: 32,292 in (21,341 cached) / 805 out tokens — $0.0103 (openrouter/openai/gpt-5.6-luna, thinking: low)

Reviewed by @weareikko/code-review v0.9.5 for commit b16a212.

Previous review runs

Previous run archived 2026-08-16T11:29:22Z

Code Review

Risk: Low — No concrete defects found; the loading helpers and their utility entry points are safe to merge.

This change adds detached image loading with decode fallback, head-appended script and link loading with shared URL-based deduplication, retry handling, and real loading errors. It also exposes the three helpers through the utilities barrel and generated subpath entry points.


Review usage: 34,895 in (25,425 cached) / 1,209 out tokens — $0.0108 (openrouter/openai/gpt-5.6-luna, thinking: low)

Reviewed by @weareikko/code-review v0.9.5 for commit 14409ca.

@github-actions

github-actions Bot commented Aug 16, 2026

Copy link
Copy Markdown

Export size

Bundled per export with peer dependencies left external, dynamic imports excluded and the output minified; sizes are gzipped.

@studiometa/js-toolkit-v4

Export Size (gzip) Diff
utils/loadLink 776 B +776 B (+100.0%)
utils/loadScript 697 B +697 B (+100.0%)
utils 8.65 kB +496 B (+5.9%)
utils/loadImage 245 B +245 B (+100.0%)
Unchanged (381)

@studiometa/js-toolkit

Export Size (gzip) Diff
(barrel) 17.44 kB
AbstractService 598 B
Base 9.06 kB
ComponentLoader 2.31 kB
DEFAULT_DIAGNOSTIC_PREFIX 102 B
DragService 2.02 kB
IDLE_TIMEOUT 57 B
KeyService 935 B
LoadService 666 B
MutationService 849 B
PointerService 1.13 kB
RafService 1020 B
ResizeService 1.12 kB
ScrollService 1.36 kB
VISIBLE_ROOT_MARGIN 72 B
autoload 2.4 kB
closestComponent 419 B
composeManifests 119 B
createApp 996 B
defineFeatures 326 B
defineManifest 512 B
fromMetaGlob 228 B
fromWebpackContext 131 B
getClosestParent 197 B
getDirectChildren 202 B
getInstanceFromElement 125 B
getInstances 187 B
getScopedGroups 104 B
importOnInteraction 926 B
importOnMediaQuery 243 B
importWhenIdle 225 B
importWhenPrefersMotion 271 B
importWhenVisible 935 B
isDirectChild 218 B
logTree 551 B
queryComponent 594 B
queryComponentAll 601 B
readEagerTokens 201 B
registerComponent 305 B
registerComponents 356 B
registerManifest 2.87 kB
registerManifests 2.89 kB
useDrag 2.05 kB
useKey 943 B
useLoad 676 B
useMutation 876 B
usePointer 1.15 kB
useRaf 1 kB
useResize 1.13 kB
useScroll 1.36 kB
utils 10.05 kB
utils/Queue 269 B
utils/SmartQueue 440 B
utils/addClass 240 B
utils/addStyle 239 B
utils/animate 3.34 kB
utils/boundingRectToCircle 206 B
utils/cache 208 B
utils/camelCase 405 B
utils/clamp 98 B
utils/clamp01 114 B
utils/collideCircleCircle 129 B
utils/collideCircleRect 192 B
utils/collidePointCircle 128 B
utils/collidePointRect 122 B
utils/collideRectRect 128 B
utils/createEaseInOut 123 B
utils/createEaseOut 91 B
utils/createElement 635 B
utils/createLocalStorage 1.32 kB
utils/createLocalStorageProvider 296 B
utils/createMemoryStorageProvider 174 B
utils/createNoopProvider 128 B
utils/createRange 115 B
utils/createSessionStorage 1.32 kB
utils/createSessionStorageProvider 288 B
utils/createStorage 1.3 kB
utils/createUrlSearchParamsInHashProvider 461 B
utils/createUrlSearchParamsInHashStorage 1.35 kB
utils/createUrlSearchParamsProvider 429 B
utils/createUrlSearchParamsStorage 1.34 kB
utils/damp 106 B
utils/dashCase 404 B
utils/debounce 122 B
utils/domScheduler 310 B
utils/ease 519 B
utils/easeInCirc 285 B
utils/easeInCubic 287 B
utils/easeInExpo 286 B
utils/easeInOutCirc 288 B
utils/easeInOutCubic 289 B
utils/easeInOutExpo 288 B
utils/easeInOutQuad 288 B
utils/easeInOutQuart 289 B
utils/easeInOutQuint 289 B
utils/easeInOutSine 288 B
utils/easeInQuad 285 B
utils/easeInQuart 286 B
utils/easeInQuint 286 B
utils/easeInSine 285 B
utils/easeLinear 77 B
utils/easeOutCirc 286 B
utils/easeOutCubic 288 B
utils/easeOutExpo 286 B
utils/easeOutQuad 286 B
utils/easeOutQuart 286 B
utils/easeOutQuint 286 B
utils/easeOutSine 286 B
utils/endsWith 128 B
utils/fold 168 B
utils/getAncestorWhere 123 B
utils/getAncestorWhereUntil 148 B
utils/getComponentResolver 140 B
utils/getOffsetSizes 194 B
utils/hasWindow 88 B
utils/historyPush 524 B
utils/historyReplace 526 B
utils/inertiaFinalValue 169 B
utils/isArray 63 B
utils/isBoolean 78 B
utils/isDefined 75 B
utils/isDev 78 B
utils/isEmpty 206 B
utils/isEmptyString 108 B
utils/isFunction 79 B
utils/isNull 68 B
utils/isNumber 91 B
utils/isObject 108 B
utils/isString 77 B
utils/keyCodes 122 B
utils/lerp 84 B
utils/loadElement 220 B
utils/loadIframe 241 B
utils/loadImage 241 B
utils/loadLink 237 B
utils/loadScript 251 B
utils/localStorageProvider 839 B
utils/lowerCase 404 B
utils/map 93 B
utils/matrix 136 B
utils/mean 126 B
utils/memo 130 B
utils/memoize 228 B
utils/memoryStorageProvider 843 B
utils/nextFrame 179 B
utils/nextMicrotask 133 B
utils/nextTick 148 B
utils/noop 62 B
utils/noopValue 76 B
utils/objectToURLSearchParams 322 B
utils/pascalCase 407 B
utils/random 93 B
utils/randomInt 113 B
utils/randomItem 234 B
utils/removeClass 242 B
utils/removeStyle 243 B
utils/round 95 B
utils/saveActiveElement 92 B
utils/scrollTo 2.31 kB
utils/sessionStorageProvider 838 B
utils/smoothTo 476 B
utils/snakeCase 406 B
utils/spring 154 B
utils/startsWith 125 B
utils/throttle 125 B
utils/toggleClass 242 B
utils/transform 347 B
utils/transition 1010 B
utils/trapFocus 441 B
utils/tween 1.72 kB
utils/untrapFocus 120 B
utils/upperCase 404 B
utils/urlSearchParamsInHashProvider 845 B
utils/urlSearchParamsProvider 839 B
utils/useScheduler 309 B
utils/wait 103 B
utils/withLeadingCharacters 135 B
utils/withLeadingSlash 142 B
utils/withTrailingCharacters 135 B
utils/withTrailingSlash 142 B
utils/withoutLeadingCharacters 122 B
utils/withoutLeadingCharactersRecursive 165 B
utils/withoutLeadingSlash 133 B
utils/withoutTrailingCharacters 122 B
utils/withoutTrailingCharactersRecursive 165 B
utils/withoutTrailingSlash 133 B
utils/wrap 122 B
version 56 B
withBreakpointManager 1.54 kB
withBreakpointObserver 1.71 kB
withDrag 2.18 kB
withExtraConfig 163 B
withFreezedOptions 187 B
withGroup 455 B
withIntersectionObserver 303 B
withMountOnMediaQuery 393 B
withMountWhenInView 347 B
withMountWhenPrefersMotion 431 B
withMutation 1010 B
withName 109 B
withRelativePointer 1.29 kB
withResponsiveOptions 2.4 kB
withScrolledInView 3.05 kB

@studiometa/js-toolkit-v4

Export Size (gzip) Diff
(barrel) 19.84 kB
BREAKPOINTS 776 B
Base 8.09 kB
DIAGNOSTICS 629 B
DRAG_MODES 162 B
EVENTS 155 B
MOUNT_ATTRIBUTE 69 B
SWAP_MODES 129 B
children 243 B
component 10.55 kB
createContext 471 B
createFallbackProvider 1.34 kB
createLocalStorage 2.34 kB
createLocalStorageProvider 1.23 kB
createMemoryStorageProvider 1.23 kB
createService 630 B
createServiceMixin 509 B
createSessionStorage 2.34 kB
createSessionStorageProvider 1.23 kB
createStorage 2.32 kB
createUrlSearchParamsInHashProvider 1.23 kB
createUrlSearchParamsInHashStorage 2.36 kB
createUrlSearchParamsProvider 1.23 kB
createUrlSearchParamsStorage 2.36 kB
defaultScheduler 1.5 kB
defineManifest 983 B
domUpdate 1.23 kB
emitExtendable 1.09 kB
fromMetaGlob 203 B
fromWebpackContext 131 B
getBreakpoints 776 B
getInstances 2.77 kB
inject 175 B
injectContext 676 B
injectContextSync 634 B
jsonSerializer 95 B
localStorageProvider 1.22 kB
memoryStorageProvider 1.23 kB
nextFrame 115 B
on 8.42 kB
perTarget 176 B
provide 178 B
provideContext 703 B
provideRootContext 748 B
read 127 B
registerComponent 10.47 kB
registerComponents 10.48 kB
registerManifest 10.56 kB
sessionStorageProvider 1.22 kB
setBreakpoints 805 B
signal 920 B
subscribeContext 1.45 kB
swap 2.63 kB
toggle 176 B
until 172 B
urlSearchParamsInHashProvider 1.22 kB
urlSearchParamsProvider 1.22 kB
useBreakpoint 1.44 kB
useDrag 3.22 kB
useInView 1.31 kB
useMediaQuery 1.05 kB
usePointer 1.3 kB
usePrefersReducedMotion 1.08 kB
useRaf 1.93 kB
useResize 1.3 kB
useScroll 2.54 kB
useScrollProgress 3.51 kB
useWindowScroll 2.52 kB
useWindowSize 1.3 kB
utils/DEFAULT_DAMP_FACTOR 109 B
utils/INERTIA_FRAME 97 B
utils/MAX_SPRING_RATIO 100 B
utils/SCROLL_AXES 100 B
utils/TRANSFORM_PROPS 137 B
utils/TRANSITION_OPTIONS 132 B
utils/camelCase 449 B
utils/capitalize 119 B
utils/clamp 133 B
utils/clamp01 149 B
utils/clampDampFactor 157 B
utils/createEaseInOut 120 B
utils/createEaseOut 91 B
utils/createElement 638 B
utils/createRange 205 B
utils/damp 211 B
utils/debounce 121 B
utils/decayOver 162 B
utils/deepmerge 312 B
utils/easeInCirc 94 B
utils/easeInCubic 81 B
utils/easeInExpo 97 B
utils/easeInOutCirc 150 B
utils/easeInOutCubic 141 B
utils/easeInOutExpo 150 B
utils/easeInOutQuad 139 B
utils/easeInOutQuart 140 B
utils/easeInOutQuint 140 B
utils/easeInOutSine 156 B
utils/easeInQuad 80 B
utils/easeInQuart 81 B
utils/easeInQuint 81 B
utils/easeInSine 104 B
utils/easeLinear 77 B
utils/easeOutCirc 121 B
utils/easeOutCubic 111 B
utils/easeOutExpo 124 B
utils/easeOutQuad 110 B
utils/easeOutQuart 112 B
utils/easeOutQuint 111 B
utils/easeOutSine 132 B
utils/enterTransition 679 B
utils/fold 200 B
utils/getOffsetSizes 268 B
utils/historyPush 380 B
utils/historyReplace 381 B
utils/inertiaDecay 199 B
utils/inertiaFinalValue 187 B
utils/inertiaStep 232 B
utils/inertiaTimeConstant 178 B
utils/isBoolean 90 B
utils/isDefined 87 B
utils/isFunction 86 B
utils/isNull 78 B
utils/isNumber 103 B
utils/isObject 115 B
utils/isString 89 B
utils/kebabCase 421 B
utils/leaveTransition 679 B
utils/lerp 120 B
utils/lowerCase 84 B
utils/map 128 B
utils/matrix 150 B
utils/mean 147 B
utils/memo 217 B
utils/noop 62 B
utils/noopValue 76 B
utils/objectToURLSearchParams 253 B
utils/pascalCase 434 B
utils/random 93 B
utils/randomInt 132 B
utils/randomItem 163 B
utils/round 130 B
utils/saveActiveElement 571 B
utils/scrollTo 1.72 kB
utils/selectorFor 2.72 kB
utils/setClassesOrStyles 218 B
utils/smoothTo 2.56 kB
utils/snakeCase 421 B
utils/spring 343 B
utils/throttle 151 B
utils/transform 286 B
utils/transition 577 B
utils/trapFocus 717 B
utils/untrapFocus 587 B
utils/upperCase 84 B
utils/wait 103 B
utils/withLeadingCharacters 142 B
utils/withLeadingSlash 152 B
utils/withTrailingCharacters 143 B
utils/withTrailingSlash 153 B
utils/withoutLeadingCharacters 127 B
utils/withoutLeadingCharactersRecursive 144 B
utils/withoutLeadingSlash 138 B
utils/withoutTrailingCharacters 129 B
utils/withoutTrailingCharactersRecursive 147 B
utils/withoutTrailingSlash 140 B
utils/wrap 154 B
viewTransition 1.66 kB
watchAttributes 1.98 kB
whenDOMSettled 2.31 kB
withDrag 3.61 kB
withInView 1.76 kB
withPointer 1.69 kB
withRaf 2.31 kB
withResize 1.7 kB
withScroll 2.93 kB
withScrollProgress 3.94 kB
write 125 B

@codspeed

codspeed Bot commented Aug 16, 2026

Copy link
Copy Markdown

Merging this PR will regress 1 benchmark

⚠️ Different runtime environments detected

Some benchmarks with significant performance changes were compared across different runtime environments,
which may affect the accuracy of the results.

Open the report in CodSpeed to investigate

⚡ 1 improved benchmark
❌ 1 regressed benchmark
✅ 139 untouched benchmarks
⏩ 141 skipped benchmarks1

Warning

Please fix the performance issues or acknowledge them on CodSpeed.

Performance Changes

Benchmark BASE HEAD Efficiency
translate3d only (x, y, z) 294.5 µs 345.5 µs -14.75%
progress update (5 transforms) 364.9 µs 309.7 µs +17.81%

Tip

Investigate this regression by commenting @codspeedbot fix this regression on this PR, or directly use the CodSpeed MCP with your agent.


Comparing feature/v4-load-helpers (b16a212) with main (a00ca20)

Open in CodSpeed

Footnotes

  1. 141 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

titouanmathis and others added 2 commits August 16, 2026 11:27
v3 shipped a generic `loadElement(src, type, { appendTo })` with five aliases
over it. The generic form is what made every one of them wrong somewhere, so
v4 ships three helpers whose rules differ because the resources differ.

`loadImage` never touches the document and awaits `decode()`, so the bitmap is
ready when the promise resolves and assigning the URL paints without jank.
`decode()` is not trusted on its own: it rejects with an `EncodingError` on
images that load — cross-origin ones in Safari, and in Chromium on any decode
failure — so the load/error events stay the authority on whether the load
worked, and the same events answer when `decode()` is unavailable.

`loadScript` and `loadLink` append to `<head>`, with no option to put them
elsewhere: a detached script never runs and a detached link is inert. Both
deduplicate on the resolved URL, so asking twice for a third-party embed
executes it once, and `loadLink` keys on `rel` as well — prefetching a URL and
loading it as a stylesheet are two intents on one href. The maps live in a
shared runtime slot, so two evaluated copies of the package cannot double-
execute an embed. A failed load is forgotten, so a network blip does not make
a URL unloadable for the life of the page.

`rel` is required, because a `<link>` without one would never settle, and a
`rel` the browser ignores resolves at once rather than waiting for an event
that will never come — Safari ignores `prefetch` entirely, and a hint is
best-effort.

Everything rejects with a real `Error` naming the source and carrying the
event as its cause. v3 rejected with a plain `{ event, element }`, so
`error.message` was `undefined` and reporters dropped the failure.

`loadIframe`, `loadEmbed`, `loadTrack` and `loadElement` itself are not
ported: no consumer, and an `<iframe>` you want in the page you write in
markup.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011nNdFD3aQhzfdm3EsCSbS9
The barrel fronts every utility module, and its spec asserts that it names
every runtime export of every module it fronts, so the three helpers join both
together. `npm run subpaths` then mints the per-symbol entry points, which is
how a consumer imports one helper without pulling the barrel's graph.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011nNdFD3aQhzfdm3EsCSbS9
@titouanmathis
titouanmathis force-pushed the feature/v4-load-helpers branch from 14409ca to b16a212 Compare August 16, 2026 11:28
@titouanmathis
titouanmathis merged commit 3182ec8 into main Aug 16, 2026
11 of 12 checks passed
@titouanmathis
titouanmathis deleted the feature/v4-load-helpers branch August 16, 2026 11:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant