Commit Graph
392 Commits
Author SHA1 Message Date
Miguel Ángel 43c56ee476 chore: bump version to 0.6.57 2026-05-29 10:34:08 -04:00
Miguel Ángel 0f938841cd fix(core,engine): guard volume probe cache and restore PCM cursor (#1119)
Two perf fixes caught in #1118 review:

1. Cache guard: probeAndCacheVolumeKeyframes now short-circuits when
   the element is already in volumeKeyframeCache. Without the guard
   every bindMediaMetadataListeners call (every 30 RAF ticks) re-probed
   all bound elements — N elements × full-composition timeline seeks at
   60 Hz regardless of whether keyframes were already known.
   bindRootTimelineIfAvailable still clears the cache on a new timeline
   capture so keyframes stay fresh when the composition is rebound.

2. PCM cursor: audioVolumeEnvelope.ts had the incremental segment
   cursor (O(N+M) overall) before #1118 extracted the interpolation into
   interpolateVolumeGain. The shared function restarts from segment=0 on
   each call — fine for the preview path (one call per RAF tick) but
   O(N×M) for the PCM path (one call per sample: 48 kHz × duration).
   Napkin math: a 10-min render went from ~30M to ~460M ops. Restored
   the inline incremental scan in the engine bake loop; engine now only
   imports normaliseEnvelope from core.
2026-05-29 10:33:10 -04:00
Miguel Ángel d3c333b383 fix(core): apply renderer volume-automation solution to preview (#1118)
Preview audio with GSAP volume fades (e.g. data-volume="0" with a
gsap.to("#bgm", {volume:0.25, ...})) played ~1s then silenced. Root
cause: syncRuntimeMedia used fallbackAuthorVolume (data-volume) on the
first tick after a clip became active, clobbering the GSAP-seeked value.
The single-clock transport seeks GSAP before syncRuntimeMedia runs, so
el.volume already holds the animated value — we just need to trust it.

Fix — three layers, matching the renderer's approach (PR #1117):

1. First-tick tracking: on the first tick a clip is active
   (previousRuntimeVolume===undefined), use currentElementVolume (GSAP's
   seeked value) instead of fallbackAuthorVolume. In production the
   transport always seeks GSAP before syncRuntimeMedia, so el.volume is
   already at the correct animated position.

2. Probed keyframes: new probeElementVolumeKeyframes() runs the same
   offline probe the renderer uses (discoverAudioVolumeAutomationFromTimeline)
   directly in the browser. init.ts calls probeAndCacheElementVolume() when
   an element is bound and a timeline is available. When keyframes are present,
   syncRuntimeMedia drives volume from the interpolated envelope — no
   GSAP-change tracking needed, no first-tick edge case, same data source
   as the renderer.

3. Shared utilities: normaliseEnvelope(), interpolateVolumeGain(), and
   probeAndCacheElementVolume() extracted to mediaVolumeEnvelope.ts and
   exported from @hyperframes/core/media-volume-envelope. The engine's
   audioVolumeEnvelope.ts imports from there — no duplicate logic between
   the renderer and the new preview path.

Fallow audit exits non-zero on inherited complexity/duplication in init.ts
functions that shifted line numbers (applyClipLayout, transportTick, etc.),
unchanged by this PR — same known false-positive pattern noted in #1117.
Lint, format, typecheck, and unit tests all pass.

53 core/media tests pass (3 updated to pre-set el.volume to match the
runtime's bindMediaMetadataListeners — corrects a missing setup step).
audioVolumeEnvelope tests (6) still pass.
2026-05-29 10:10:12 -04:00
Miguel Ángel bc3701f590 chore: bump version to 0.6.56 2026-05-28 23:51:08 -04:00
Miguel Ángel b1f9587aa1 chore: bump version to 0.6.55 2026-05-28 20:58:58 -04:00
Miguel Ángel 789d1d4775 fix(studio): cover GSAP editor target-resolution limitations (#1116)
Follow-up to #1115. Makes the Design-panel editor recognise every target
shape real compositions use. The panel stays behind STUDIO_GSAP_PANEL_ENABLED
(default off) — no flag change here.

- Array targets: tl.to([a, b], {...}) resolves to a CSS group selector
  (".a, .b"). The source array is never rewritten — the joined string is for
  display/matching only; edits still touch just the vars object.

- Chained calls: tl.to(a, ...).to(b, ...) — the matcher now walks the member
  chain to its timeline root, so every link is captured (previously only the
  first). Deletion is chain-aware: it splices out the single targeted link and
  re-points the chain instead of dropping the whole statement.

- gsap.utils.toArray("sel") resolves like querySelectorAll, inline or via a
  variable binding.

- Lexical scoping: element-variable resolution is now per-scope (walks the
  enclosing function/program chain) instead of a flat map. Fixes silent
  wrong-resolution when two IIFEs reuse a variable name, and unlocks
  multi-scene files. (Addresses review: flat-binding-scope.)

- forEach/map callback params (items.forEach(el => tl.to(el, …))) and items[i]
  indexing resolve to the collection's selector, so loop-generated tweens are
  editable.

- Panel matching: an element matches a tween when its id/selector is any member
  of a comma-group target, so either element of an array/toArray tween surfaces
  the shared animation.

- Review items: mutation parse failures now console.warn instead of swallowing
  silently; buildTweenStatementCode no longer emits duration on `set`; the
  id-only serialize-side filter is renamed getAnimationsForElementId to
  disambiguate from the panel's id-or-selector matcher; added fromTo round-trip
  and variable-target overlap-lint tests.

Genuinely runtime-only targets (template-literal selectors, unbounded loops)
still skip gracefully — they can't be resolved or matched statically.
2026-05-28 20:57:59 -04:00
Miguel Ángel 4de054e7d4 fix(studio): make GSAP tween editing work on real compositions (#1115)
The Design-panel GSAP editor only recognized tweens written as
tl.to(".selector", {...}) with inline string-literal targets, in a
contiguous block, with no interleaved setup. Every scaffolded
composition instead targets tweens through element variables
(const kicker = root.querySelector(".kicker"); tl.to(kicker, {...})),
wraps the script in an IIFE, and interleaves gsap.set() calls — so the
parser returned zero animations and the panel was inert.

Three coordinated fixes make it work end to end:

- Parser read: resolve querySelector / querySelectorAll / getElementById
  variable targets (and inline lookup calls) back to their CSS selector,
  so variable-targeted tweens are recognized.

- Parser write: replace the full re-serialize (preamble + tweens +
  postamble) with in-place recast AST mutation. Edits now touch only the
  targeted tween's vars/position node and reprint, preserving every
  surrounding statement — gsap.set calls, element declarations, the IIFE
  wrapper, comments and formatting. Previously the first edit would
  discard all of that.

- Linter: build overlap/clip windows directly from the parser's
  structured animations instead of a regex walk paired positionally with
  the parsed list. The old pairing skipped variable targets and would
  drift once the parser started returning them. Removes the now-dead
  regex meta helpers.

- studio-api: extractGsapScriptBlock now searches inside <template>
  content (sub-compositions wrap markup + the GSAP script in a template,
  which linkedom's querySelectorAll doesn't descend into), and the
  frontend matches tweens to the selected element by id OR selector
  rather than id only (class-targeted elements have no id).

Verified end to end against a real 10-scene project: all compositions
now parse (previously 0), the panel populates editable tween cards, and
property/duration/ease edits round-trip while leaving the rest of the
script byte-for-byte intact.
2026-05-28 20:54:44 -04:00
Miguel Ángel 2f3ab9f4c9 chore: bump version to 0.6.54 2026-05-28 19:18:34 -04:00
Miguel Ángel fb2e21090f feat(studio): GSAP tween editing in Design panel (#1102)
* feat(studio): GSAP tween editing in Design panel

Add a GSAP animation editor to the studio Design panel: select an element,
view and edit its tweens (properties, easing, timing), add/delete animations,
and drag custom bezier speed curves — all persisted back to the composition
HTML. Gated behind VITE_STUDIO_ENABLE_GSAP_PANEL.

Parsing of existing GSAP source now uses a recast + Babel AST parser instead of
regex, giving scope resolution, stable tween IDs, and round-trip preservation of
extras and unresolved raw values.

recast compiles to CommonJS that calls require("fs"), which breaks browser and
Vite SSR bundles. To contain it, @hyperframes/core is split into an isomorphic
layer and a Node-only AST layer:

- gsapSerialize.ts holds the recast-free helpers (serialization, keyframe
  conversion, validation, shared types). htmlParser.ts is now fully isomorphic.
- parseGsapScript and the script-mutation helpers live in gsapParser.ts,
  reachable only via the @hyperframes/core/gsap-parser subpath, loaded
  server-side by the studio-api mutation routes and the linter via dynamic
  import (recast stays external under SSR).
- The barrel and the gsap-constants subpath are recast-free, so studio browser
  bundles never trace recast.

Adds AST parser unit + stress coverage and e2e helpers for the panel.

* fix(lint): await async lintHyperframeHtml in all callers

lintHyperframeHtml became async (gsap rules use dynamic import)
but lintProject and check-hyperframe-static weren't awaiting it,
causing typecheck failures and runtime crashes in CI.

Also wire LintRule type in gsap rules to fix fallow unused-type
finding, and suppress render.ts exported-for-tests symbols.
2026-05-28 19:16:34 -04:00
Miguel Ángel e16f916448 chore: bump version to 0.6.53 2026-05-28 17:06:50 -04:00
Miguel Ángel e0cb8fcee3 fix(core): remove 1800s hard cap on timeline duration that silently truncated long compositions (#1114)
The runtime had a maxTimelineDurationSeconds field defaulting to 1800
(30 minutes) that clamped the TransportClock duration. Any seek beyond
this cap was silently clamped, so GSAP tweens starting past ~1700s
never received their totalTime() call and stayed at their pre-tween
state (e.g. opacity:0).

The data-duration attribute is the authored source of truth. The loop-
inflation guard (timelineLooksLoopInflated) already handles the infinite
repeat:-1 case this cap was meant to protect against.

Closes #1107
2026-05-28 16:26:40 -04:00
Miguel Ángel 7be4f92f18 chore: release v0.6.52 2026-05-27 20:23:26 -04:00
Miguel Ángel 2d7b9e5245 fix(core): guard timeline method calls for non-conformant objects (#1098)
* fix(core): guard timeline method calls for non-conformant objects

User compositions can register timeline-like objects on window.__timeline
where .duration is a number property (not a function) and .pause/.play
may be missing entirely. The runtime player called these unconditionally,
causing ~166 "duration is not a function" and ~38 "pause is not a function"
errors per day.

Add safeNum() and safeVoid() helpers that check typeof before calling,
falling back to reading numbers as properties and silently skipping
missing void methods. Applied consistently across all timeline method
call sites in player.ts.

* fix(core): add observability for non-conformant timeline properties
2026-05-27 20:16:38 -04:00
Miguel Ángel d8ce2e4b50 chore: release v0.6.51 2026-05-27 12:15:38 -04:00
Miguel Ángel 5cd4db07e3 chore: release v0.6.50 2026-05-27 15:28:56 +00:00
Miguel Ángel 7ea4d1c131 chore: release v0.6.49 2026-05-27 01:45:45 -04:00
Miguel Ángel f19d6fd471 feat: CLI observability + fix studio save failures on JS-created elements (#1091)
* feat(core): add probeElementInSource for source-existence checks

* feat(core): add probe-element endpoint for source-existence checks

* feat(studio): gate editing capabilities on source existence

* fix(studio): enrich save_failure telemetry with target details

* feat(studio): async selection resolution with source probe

Make `resolveDomEditSelection` async and wire a `probeSourceElement` call
into the selection path so elements generated by scripts (not present in the
source HTML) are detected early and have all edit capabilities disabled with
a clear reason message ("This element is generated by a script and cannot be
edited visually.").

Part A – core probe logic:
- `domEditingLayers.ts`: `resolveDomEditSelection` is now async; calls
  `probeSourceElement` (POST /api/projects/:id/file-mutations/probe-element/:file)
  when `projectId` is supplied and the element has a stable id/selector.
  `existsInSource: false` flows into `resolveDomEditCapabilities`, which
  disables all write capabilities with the appropriate reason.
- `domEditingLayers.ts`: `refreshDomEditSelection` promoted to async.
- `files.ts`: new `probe-element` route; extracted `resolveProjectPath`,
  `resolveFileMutationContext`, `writeIfChanged`, and `parseMutationBody`
  helpers to eliminate repeated boilerplate across remove/patch/probe handlers.

Part B – caller propagation (all eight consumer sites):
- `useDomSelection.ts`: `buildDomSelectionFromTarget`,
  `resolveDomSelectionFromPreviewPoint`,
  `buildDomSelectionForTimelineElement`, `handleTimelineElementSelect`,
  `refreshDomEditSelectionFromPreview`, and
  `refreshDomEditGroupSelectionsFromPreview` all made async; `projectId`
  forwarded into `resolveDomEditSelection`.
- `useDomEditCommits.ts`, `useDomEditTextCommits.ts`: updated
  `buildDomSelectionFromTarget` parameter type; added `await` at call sites.
- `useDomEditSession.ts`: inner `syncSelectionFromDocument` made async; fire
  with `void` to satisfy the surrounding effect.
- `usePreviewInteraction.ts`: `handlePreviewCanvasMouseDown` and
  `handlePreviewCanvasPointerMove` made async (React ignores handler return
  values, so this is safe).
- `useStudioUrlState.ts`: deferred `buildDomSelectionFromTarget` call
  converted to `.then()` chain with `void` prefix so the effect stays sync.
- `LayersPanel.tsx`: `seekToLayer`, `handleSelectLayer`, and
  `handleLayerHover` made async.
- `DomEditOverlay.tsx` / `useDomEditOverlayGestures.ts`: `onCanvasPointerMove`
  return type widened to `Promise<DomEditSelection | null>`; pointer-down
  handler falls back to `hoverSelectionRef.current` (always populated by a
  prior hover) instead of awaiting the async move callback inline.

Part C – test and tooling fixes:
- `lefthook.yml`: filesize hook shell loop explicitly skips `*.test.ts/tsx`
  files as a guard against a lefthook v2.1.6 bug where `exclude` patterns are
  not applied to `{staged_files}` in shell scripts.
- `domEditing.test.ts`: all `it()` blocks calling `resolveDomEditSelection`
  made async with `await`.
- `DomEditOverlay.test.ts`: mock updated to return `Promise.resolve(selection)`
  and `hoverSelection` pre-seeded so pointer-down test works with the new
  hover-first path.
- `studioUrlState.test.ts`: `buildDomSelectionFromTarget` mocks wrapped in
  `Promise.resolve()`; seek/selection hydration test made async with
  `await act(async () => { await Promise.resolve(); })` to flush microtasks.

* feat(cli): add global error handlers for crash telemetry

Register process-level uncaughtException and unhandledRejection handlers
that fire trackCliError so unhandled crashes are captured in telemetry.
Add the trackCliError function to events.ts and re-export it from the
telemetry barrel.

* feat(cli): track per-command success/failure and duration

* test(core): add integration test for JS-created element probe scenario

* fix: address PR review feedback

- uncaughtException handler now calls process.exit(1) after flushing
- cli_command_result uses real exit code from process "exit" event
- drop stack_trace from cli_error (contains filesystem paths)
- skip source probe during hover — only probe on click/selection
- format .fallowrc.jsonc

* fix(cli): restore stack_trace in cli_error telemetry

* fix(cli): use captured module refs in exit handlers instead of dead import()
2026-05-27 01:44:31 -04:00
Miguel Ángel 7cde0d9554 chore: release v0.6.48 2026-05-26 23:46:36 -04:00
Miguel Ángel 2d0acb3494 chore: release v0.6.47 2026-05-26 20:26:05 -04:00
Miguel Ángel c53552876e fix(core): patch resolveMediaWindowDurationSeconds + extract helper
Fix the 4th unguarded resolveStartForElement call site in
resolveMediaWindowDurationSeconds that inflated the timeline duration
floor for pip compositions. Extract resolveMediaStartSeconds helper
to consolidate the data-hf-auto-start guard across all call sites.
2026-05-26 14:25:39 -04:00
Miguel Ángel 1d0b18587d fix(core): extend media start fix to all consumers, guard auto-start
Narrow the raw data-start read to media elements without
data-hf-auto-start (explicitly authored global coordinates). Elements
with auto-injected data-start="0" remain composition-local via the
resolver. Apply consistently across all three consumers:
- visibility loop (init.ts)
- refreshRuntimeMediaCache start/duration (init.ts)
- resolveMediaWindowEndSeconds (timeline.ts)

Add regression test for auto-injected data-start="0" inside a
late-starting host to prove it doesn't regress.
2026-05-26 14:25:39 -04:00
James XiaoandClaude Sonnet 4.6 1be2a584b4 fix(core): use raw data-start for media elements in preview visibility loop
For video and audio elements, data-start is authored in global (composition-root)
time — the same contract used by the render pipeline's discoverMediaFromBrowser,
which reads the raw attribute directly. Previously, the visibility loop called
resolveStartForElement which adds the nearest ancestor composition's global start
on top, causing a double-offset that kept pip-wired media permanently hidden when
the host composition did not start at t=0.

Example: a pip video with data-start="45.40" inside a host composition that also
starts at data-start="45.40" resolved to 90.80, so the video was always hidden
during its actual [45.40, 52.46] window.

Non-media elements (divs, sections, etc.) continue to use the accumulating
resolver because their data-start values are local to their composition.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-26 14:25:39 -04:00
Miguel Ángel 60cb9552e4 chore: release v0.6.46 2026-05-25 23:49:33 +00:00
Miguel Ángel 9a4a00582c chore: release v0.6.45 2026-05-25 19:45:52 +00:00
Miguel Ángel 0de17a1f66 fix(core): restore IIFE with </script> escaping for composition scripts
The Function constructor (3bb0d1ef) was a security hardening to prevent
</script> injection, but it broke sub-composition DOM proxy scoping.
This restores the inline IIFE (preserving closure scope) while adding
</script> → <\/script> escaping to maintain the injection prevention.

Updates tests to match the new IIFE output shape.

Closes #1074
2026-05-25 15:44:35 -04:00
Miguel Ángel ccee8df4e1 fix(core): revert Function constructor back to inline IIFE for composition scripts
The Function constructor (3bb0d1ef) breaks sub-composition scripts that
call document.getElementById() — the constructor creates functions with
global scope, losing access to the composition-scoped DOM proxy. Native
method calls on proxy-returned elements throw "Illegal invocation".

Reverts to the inline IIFE that preserves the closure over __hfScoped*
variables. The original motivation (handling </script> in source) is
already handled by the compiler's script bundling path.

Closes #1074
2026-05-25 15:44:35 -04:00
Miguel Ángel c46adb52e2 chore: release v0.6.44 2026-05-25 16:56:50 +00:00
Miguel ÁngelandClaude Sonnet 4.6 e4e2234303 chore: release v0.6.43
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 13:35:55 +00:00
Miguel Ángel 8b776cb3c8 Merge pull request #1069 from func25/fix/preview-reuse-and-waapi-seek-baselines 2026-05-25 09:33:54 -04:00
func25 e10ec358f8 fix(core): correct WAAPI rediscovery seek baselines and preview reuse invalidation 2026-05-25 17:51:47 +07:00
func25 3bb0d1efd9 fix(core): wrap bundled composition scripts as string literals 2026-05-25 15:41:13 +07:00
func25 e101b47be4 fix(core): preserve sub-composition script order in preview bundles 2026-05-25 15:27:52 +07:00
func25 bbb7a4d965 fix(core): inline local sub-composition scripts in preview bundles 2026-05-25 15:12:11 +07:00
Miguel Ángel d2b915be9e chore: release v0.6.42 2026-05-24 17:36:30 -04:00
Miguel Ángel 947bf6cc78 Merge remote-tracking branch 'origin/main' into fix/audio-volume-automation
# Conflicts:
#	packages/engine/src/services/audioMixer.test.ts
2026-05-24 16:46:52 -04:00
Miguel Ángel 167a222318 fix: support animated audio volume 2026-05-24 16:39:04 -04:00
Miguel Ángel 526709cad2 fix(cli): handle encoded lint asset paths 2026-05-24 16:31:45 -04:00
Miguel Ángel 7461f1df30 chore: bump version to 0.6.41 2026-05-24 14:26:59 -04:00
Miguel Ángel 1d09e6ff36 fix(core): set __renderReady unconditionally after binding attempt
The capturedTimeline guard broke CSS/WAAPI/Lottie compositions that
have no GSAP timeline — __renderReady was never set, causing the
parity harness to timeout after 30s.

renderSeek works with or without a GSAP timeline (adapter-only
seeking), so the correct invariant is "timeline binding was
attempted" not "a timeline was found." Set __renderReady
unconditionally in all three paths, after bindRootTimelineIfAvailable
has run.
2026-05-24 13:42:30 -04:00
Miguel Ángel 3347486ae9 refactor(core): remove redundant as casts using window.d.ts declarations
window.d.ts already declares __timelines, __player, __playerReady,
and __renderReady on the global Window interface. The casts in
init.ts and init.test.ts were re-asserting the same types.

- Add __hfRuntimeTeardown to window.d.ts (used 6x in init.ts)
- Remove runtimeWindow cast variable from init.ts — use window directly
- Remove all (window as Window & { __player?: ... }).__player casts
  from init.test.ts — window.__player is already typed as PlayerAPI
- Remove all (window as Window & { __timelines?: ... }).__timelines
  casts from init.test.ts — window.__timelines is already typed
- Remove (window as Window & { __playerReady/renderReady }}) casts
  from init.ts — already declared globally
2026-05-24 13:36:57 -04:00
Miguel Ángel e7d0b392c7 fix(core,cli): address review — guard __renderReady, drop pre-quantization, add tests
- Guard __renderReady with `if (state.capturedTimeline)` in all three
  paths (setTimeout(0) and .finally() were setting it unconditionally
  even when bindRootTimelineIfAvailable returned false)
- Remove redundant fps=30 pre-quantization in snapshot — renderSeek
  already calls quantizeTimeToFrame internally with the runtime's
  canonicalFps, so pre-quantizing was double-quantizing at a
  potentially wrong grid
- Add regression tests: __renderReady is set when timeline exists,
  stays undefined when no timeline is available
2026-05-24 13:31:36 -04:00
Miguel Ángel 16e049b320 fix(cli): address review — fps comment, fileServer cross-ref, duration note
- Add comment explaining hardcoded fps=30 (runtime's canonicalFps
  default, not exposed on PlayerAPI)
- Add cross-reference comments between init.ts and fileServer.ts
  explaining their different __renderReady timing semantics
2026-05-24 13:27:54 -04:00
Miguel Ángel b2828e48e5 fix(core,cli): defer __renderReady until root timeline is bound
The runtime set __renderReady at the same time as __playerReady,
before the root timeline was bound. Consumers waiting for
__renderReady (the render-safe signal) could observe a player with
no captured timeline, making renderSeek a no-op.

Root cause: init.ts set both flags together, but timeline binding
happens later — synchronously via bindRootTimelineIfAvailable(),
via a deferred setTimeout(0) for bundled compositions, or
asynchronously via loadExternalCompositions().

Fix in init.ts:
- Remove __renderReady from the __playerReady assignment
- Set it after bindRootTimelineIfAvailable() when timeline is found
- Set it in the setTimeout(0) deferred path
- Set it in the external compositions .finally() path

Fix in snapshot.ts:
- Wait for __renderReady (truthful signal) not __timelines
- Use renderSeek() with frame quantization, not seek()
- Tick the GSAP ticker after seeking
- Await document.fonts.ready before capturing

Closes #1047
2026-05-24 13:13:28 -04:00
func25 d3127a7507 fix(core): preserve static methods on classes exposed through scoped window 2026-05-24 22:50:40 +07:00
Miguel Ángel 47d57bff10 chore: bump version to 0.6.40 2026-05-23 15:01:38 -04:00
Miguel Ángel 3983d4e7bf fix(core): set explicit dimensions on data-hf-inner-root wrapper
The prepareFlattenedInnerRoot function creates a wrapper div when
inlining sub-compositions. This wrapper had no width/height, which
broke CSS height:100% chains — any sub-composition using percentage
heights with flexbox centering would collapse to 0px and render
content at the top instead of centered.

Read data-width/data-height from the inner root and set matching
pixel dimensions on the wrapper's inline style. Applied in both the
compiler (server-side bundling) and the runtime (browser-side
composition loader).

Adds a producer regression test with a centered card sub-composition
that fails without this fix.
2026-05-23 14:53:16 -04:00
James 179b09ec9d chore: release v0.6.39 2026-05-23 17:30:05 +00:00
Miguel Ángel 3560678bb2 chore: bump version to 0.6.38 2026-05-23 00:13:50 -04:00
James 258bd6256c chore: release v0.6.37 2026-05-22 23:17:08 +00:00
Miguel Ángel 154359d95d chore: bump version to 0.6.36 2026-05-22 13:37:28 -04:00