Changelog

What's new

Release notes for Zestial.

Changelog

All notable changes to Zestial. This file is the source of truth for the /changelog page on zestial.app and stays in sync with GitHub releases.

The format is loosely based on Keep a Changelog; dates are ISO-8601.

1.0.0 - 2026-08-24

The first public release: everything added since 0.1.0, plus a pre-launch audit and the fortnight of repair it produced.

Added

Effects and overlays

  • 42 effects (30 scene + 12 post), up from 15. A shipped GLSL library of

    seventeen scene effects and eight post passes, authored in the same custom-effect

    format users write in.

  • Three new overlays. Lyrics (LRC), a Now Playing card, and a Waveform Strip.

  • Real prerendered thumbnails for every effect, overlay and Starter Look.

    Every gallery tile is a genuine frame of the real effect, not an illustration.

  • Categorised Add gallery so 42 effects stay findable.

The editor

  • Undo / redo (⌘Z / ⌘⇧Z). Slider drags collapse into one step; applying a

    Starter Look is one step, not fifteen.

  • Duplicate a layer or overlay (⌘D), bindings and all.

  • Per-layer opacity and blend modes (Normal / Add / Screen). Opacity is

    bindable, so a layer can fade in on the bass.

  • Layer in/out points with fades. Bars for the intro, ribbons for the drop.

  • Save your own presets and Looks. A local library that works signed out and

    offline; Zestial Cloud syncs it across devices.

  • Project palette. New layers seed their colours from a chosen set.

  • Per-track input gain, so a quietly-mastered track no longer makes the

    visualizer look broken.

  • Surprise me. Roll a complete, audio-reactive composition in one click.

  • Relink memory. Reopening a project on the same device finds its audio

    again instead of asking every time. Zestial remembers where the files sit,

    not the files themselves, and nothing about them leaves your machine. Firefox

    and Safari lack the browser API this needs and still ask on each open.

Export

  • Choose what to render: one track or the whole playlist, and an in/out range

    within it. Effects with trails get a warm-up run so a mid-song in-point doesn’t

    open on an empty accumulator.

  • Named platform presets. Reels/TikTok, YouTube, Square, Spotify Canvas.

  • Still frames. Save the current frame as a PNG at project resolution.

  • Every browser that can export video exports it with sound. Safari 16.4

    through 18.7 can encode the picture but not the audio, and Zestial now brings

    its own AAC encoder rather than handing those browsers a silent film — or, as

    it actually did, promising one and then refusing the render outright.

Everywhere

  • A phone shell built for phones. Bottom sheets, a real player, swipe to

    delete, and every keyboard-only action given a touch route.

  • Prism Z, the brand mark, now used everywhere: favicons, app chrome, touch

    icon, social cards.

  • A demo track, so the app does something the moment you open it.

  • Installable, and works offline. The shell is precached; local-first is

    now literally true.

  • Portrait 9:16 canvases, so Reels, TikTok and Spotify Canvas dimensions work

    end to end.

  • Zestial Cloud (optional, opt-in, entirely separate from the editor).

    Passwordless sign-in, synced projects and presets, unlisted share links, render

    history. The editor stays free, un-watermarked and fully usable without an

    account.

  • Share links. Send someone a composition and they can open it. Audio is never

    included. They bring their own track.

  • Deep links from the site into the app, which now arrive with audio loaded.

  • Export and delete your Cloud data from the Cloud panel.

  • Delete a project from Cloud. Rows in My projects gain a delete control,

    behind a confirmation. Your local copy is untouched.

  • Use another account. Switching accounts no longer means finding Sign out

    underneath your project list, which under a subscription was off the bottom

    of the panel.

  • Marketing site at zestial.app and the single-origin deploy serving the app

    under /app.

Changed

  • The local-first promise is stated precisely. Your audio never leaves your browser — it is decoded, analysed, rendered and exported on your own machine, and nothing in the cloud ever receives a track. Earlier copy shortened that to “no upload, no account”, which was written before Zestial Cloud existed and was too broad once it did: sync, share links and accounts are real, and they are opt-in. The editor is complete without any of them.

  • Motion means the same thing at every frame rate. Trails, decay and

    beat-driven kicks were measured per frame rather than per second, so the same

    composition ran twice as fast on a 120 Hz display as in a 60 fps export. They

    are time-based now: what you preview is what renders.

  • Prices read $4 USD and $36 USD, with a note that some regions are charged

    in local currency at checkout. Polar converts at checkout, and an unqualified

    dollar sign made that look like a surprise rather than a conversion.

  • Filenames also match on a normalised form now, so a re-encode or a re-tag

    still lines up with the track a project is asking for.

  • Gallery cards no longer run their live preview on hover when the system asks

    for reduced motion. The tile beneath it is a real frame of the same effect, so

    almost nothing is lost.

  • Site copy edited throughout.

Fixed

A pre-launch audit read the codebase end to end and found 5 high, 23 medium and

around 30 low-severity defects. Almost all of them are repaired below. They are

grouped by what they cost you rather than by where they lived.

Losing work

  • Applying a Starter Look deleted your custom shaders. It replaced the whole

    custom-effect set rather than the layers, and no undo brought them back.

  • Autosave could drop the last second and a half of work — the debounce had

    no path for a tab that closes. It now flushes on the way out.

  • One unreadable file in a batch rejected the whole batch. Drop ten tracks

    with one bad file and you got zero tracks.

  • Undo could resurrect a removed track without its audio, leaving a playlist

    entry that pointed at nothing.

  • The restore prompt could overwrite a project a share link had just opened.

Export

  • Dismissing the save dialog reported “Export complete” and discarded a

    finished render with no way to get it back. It now says what happened and

    offers the file again.

  • A playlist export rendered untouched tracks with no beat data, so every

    beat and beat-phase binding sat perfectly still through them — after a preview

    that looked right.

  • The screen is now genuinely held awake during a render. The dialog had been

    promising it; nothing was implementing it.

  • Square (1:1) projects exported as 1920×1080 instead of a square video.

  • Multi-track export estimates were understated. The dialog sized the render

    from the current track while rendering the whole playlist.

  • The done panel named a file that did not match the one saved.

  • A multi-track export applied one track’s input gain to all of them.

How things render

  • Six post effects were authored against pixels and handed a 1×1 resolution.

    Scanlines collapsed into whole-screen tints, oil paint sampled the entire frame

    as one brush, and halftone and anamorphic flare skipped aspect correction on

    anything but a square. All six get the real output size now — and VHS Tape and

    Subspace Resonance no longer alias into a two-pixel comb at 1080p.

  • Spectrum-driven custom post effects sampled a frozen first frame forever.

  • The spectrum assumed every track was 48 kHz, so 44.1 kHz material was

    mapped about 9% sharp — most of a semitone, across the whole display.

  • Editing any layer tore down and rebuilt the entire post chain, reallocating

    bloom’s render targets on every slider tick.

  • **Moving the window to a display of a different density, or zooming the

    browser, left post effects sampling a stale frame.**

  • A lost graphics context left a permanently black stage. It now explains

    itself and restores, and cancels an in-flight export rather than stalling.

  • Browsers without WebGL2 got a React stack trace instead of an explanation.

Playback

  • Fullscreen kept playing but stopped listening. The end of a track did

    nothing there: no auto-advance, no loop, and a play button that had become a

    no-op until you left fullscreen.

  • **Switching tracks mid-analysis could stamp the previous track’s tempo onto the

    new one**, so beat-synced layers ran to a grid from a different song.

  • A backgrounded tab stopped advancing the playlist — a track ended, nothing

    followed it, and you came back to silence.

  • Per-track input gain ratcheted to silence or to full instead of settling

    where you put it.

  • Pressing play after the last track ran out restarts it. It used to start

    the audio at its final sample, so the button appeared dead.

  • Removing the track that was playing left the transport showing the old one.

The editor

  • Parameter sliders can be grabbed anywhere they are drawn. The invisible

    control was a third the height of the row it sat in, so presses landed in a

    dead band, and the browser answered the drag with a “no drop” cursor.

  • The meter beside a bound parameter shows what the binding actually does. It

    had its own private copy of the mapping maths, and the two had drifted apart.

  • Bar-beat ticks appear for a freshly loaded track. Tempo was read once while

    detection was still running, so the answer was zero and nothing recomputed it.

  • The whole editor can be driven from a keyboard. Dialogs take focus when

    they open and hand it back when they close, layers can be selected without a

    mouse, and controls that appear on hover now also appear on focus.

  • Fullscreen scrub-drag no longer leaves listeners behind when a gesture is

    interrupted, which had left stray pointer movement seeking the track.

  • Coming back from checkout flips the header to CLOUD on its own. It waited

    for the window to regain focus, which loses a race with the payment webhook

    often enough to matter.

Writing custom effects

  • **An imported effect whose id collided with a built-in silently added the

    built-in instead.**

  • **The shader validator reported everything as valid when the graphics context

    was lost** — the one moment it most needed to fail.

  • A number at or above 1e21 in a node graph produced invalid GLSL, and the

    error pointed at a line the author never wrote.

  • Custom-effect documentation described a uniform that does not exist and omitted

    three that do.

0.1.0 - 2026-06-21

The first public-readiness milestone. The core loop (drop audio → build a reactive composition → preview → export MP4) is complete and hardened.

Added

  • Session autosave & restore. Your in-progress work is snapshotted locally and offered back after an accidental reload, with audio relinked on the spot.
  • Export error surfacing. Encoder and muxer failures now show a clear message instead of a silent stall, and the UI returns to a usable state.
  • Real export capability detection. Browsers that can genuinely encode H.264/AAC are ungated (Chromium today, Safari 26+), with a friendly notice on the rest.
  • Feedback loops. A tempo-analysis indicator, and an inline “showing last good — fix errors” badge in the custom-effect editor.
  • Licensing & shipping basics. PolyForm Noncommercial license (your exported videos are yours to use commercially), version + metadata, social cards.

Fixed

  • A global unhandled-rejection safety net so background async failures surface as a toast instead of vanishing.