Commit Graph
5010 Commits
Author SHA1 Message Date
Miguel Ángel a369708fa5 fix(studio): open projects named with a colon and keep preview assets for names with # (#4829)
* fix(studio): open projects whose folder name has a colon

* fix(studio): open projects named with a colon or '#' in Studio and its preview

* fix(studio): refuse a Windows stream id reached through a session alias

One adapter rule, isServableProjectId, now guards the requested id, a
session's projectId and the folder listing. The bundler-failure preview
places its <base> inside an attributed <head>, and the media metadata
cache keys on the encoded URL so ids with ':' can't collide.
2026-09-30 23:21:53 -07:00
Miguel Ángel ed20d67b16 chore: release v0.8.102 (#4835) v0.8.102 2026-09-30 23:04:17 -07:00
Miguel Ángel 4c768186b8 fix(studio): the properties panel no longer hands GSAP an element it does not position (#4830)
* fix(studio): the properties panel no longer hands GSAP an element it does not position

* refactor(studio): drop the comment the comment-share ratchet counts

* fix(studio): the panel also skips GSAP's transform-origin channels on an element GSAP does not position

* refactor(studio): delete the unused second list of GSAP transform channels
2026-09-30 22:52:03 -07:00
Miguel Ángel c0f2780b8e feat(studio): hosts can apply the canvas menu's z-order through useDomEditZOrder (#4793)
* feat(studio): hosts can apply the canvas menu's z-order through useDomEditZOrder

* style(studio): format useDomEditZOrder

* docs(studio): trim the z-order hook's moved comments

* fix(studio): a read-only preview never reorders or deletes, from any host control

The session's z-reorder and delete commits now refuse in a read-only preview, so the lane mirror is skipped too. useDomEditZOrder is off in read-only and for a detached element, apply reports false when nothing is saved, a same-class sibling keeps its selector index, and the hook returns a stable object.

* fix(studio): read-only stops canvas and host z-order only, and a reloaded selection

The session's z-reorder and delete commits no longer refuse in a read-only preview: the timeline's lane-to-z sync goes through the same commit and stays editable. The refusal stays in useDomEditZOrder and the overlay. A selection from before a preview reload, whose document no window shows, gets no z-order.
2026-09-30 22:51:35 -07:00
Miguel Ángel be80a30802 chore: release v0.8.101 (#4831) v0.8.101 2026-09-30 21:12:22 -07:00
Miguel Ángel f2ef034fab fix(studio): one Cmd+Z pressed inside the preview takes one undo step, not three (#4824)
* fix(studio): one Cmd+Z pressed inside the preview takes one undo step, not three

* fix(studio): one undo per key press, tracked, with the preview's extra history listener removed
2026-10-01 03:46:55 +00:00
Miguel Ángel af314cbc3d fix(studio): start timeline drops on an empty lane at 0 and snap to clip edges (#4811)
* fix(studio): start timeline drops on an empty lane at 0 and snap to clip edges

A file, asset, block or composition dropped on an empty lane, a new lane, or an empty timeline now starts at 0 instead of the pointer time. On a lane with clips the start snaps to the nearest clip edge within TIMELINE_SNAP_PX. The drop preview and the committed drop share the placement.

* fix(studio): keep the drop file's comment share flat

* fix(studio): a drop snaps to clip edges only while the timeline magnet is on
2026-10-01 03:42:15 +00:00
Miguel Ángel 6ba35c9e3b fix(studio): undo and redo show on the next frame instead of after the server answers (#4798)
* fix(studio): undo and redo show on the next frame instead of after the server answers

* fix(studio): an undo pressed before the history caught up still restores in place

* fix(studio): undo and redo of an edit inside a nested composition apply in place

* fix(studio): undo of a nested edit reloads when its composition has a GSAP script

* refactor(studio): a nested restore with a GSAP script has no in-place scope

* fix(studio): an undo after an outside write shows the file the server restored
2026-10-01 03:36:08 +00:00
Miguel Ángel 7719f973c0 feat(studio): a host app can mount the dock with its own panels, storage key and width (#4812)
* feat(studio): a host app can mount the dock with its own panels, storage key and width

* refactor(studio): the dock keeps a host layout through Studio's own preference storage

* fix(studio): a dock reopens a panel beside the preview only when its usual neighbour is not in the dock
2026-10-01 03:34:47 +00:00
Miguel Ángel e29433c77e chore: release v0.8.100 (#4828) v0.8.100 2026-09-30 20:24:37 -07:00
Miguel Ángel c8f848f984 fix(studio): move and nudge an element without GSAP by its own CSS translate (#4799)
* fix(studio): move and nudge an element without GSAP by its own CSS translate

* fix(studio): route a GSAP-free move through the on-element writer and meet the file checks

* fix(studio): a drag press on a GSAP page leaves an element GSAP does not position alone

* fix(studio): a press reads a percent or calc translate without touching the element

* refactor(studio): import POSITION_CHANNELS from its owner instead of a re-export

* test(studio): share the drag press helper in the start gesture tests

* refactor(studio): split the translate length reader into term and sum

* test(studio): cover each translate form the reader resolves or hands to Chrome

* test(studio): bank the edit accuracy cases plain-CSS moves now pass

baseline.json is the edit-accuracy-gate artifact of the CI run at 2a84c103, copied byte for byte: 60 newly passing cases, none regressed. The commits since change no behaviour (a reader split and tests).
2026-09-30 20:09:23 -07:00
Miguel Ángel c777f36d47 fix(studio): a text edit saves to the element that was edited, or says why it could not (#4803)
* fix(studio): a text edit saves to the element that was edited, or says why it could not

* fix(studio): saving a text edit keeps whatever else is selected as it was
2026-10-01 00:54:21 +00:00
Miguel Ángel 097250e11c test(studio): gate edit accuracy in CI against the base branch baseline (#4765)
* test(studio): gate edit accuracy against the base branch baseline

Work in progress: the ratchet and its tests; the CI job follows.

* test(studio): run the edit accuracy gate in CI across eight shards

Shares the built CLI from the Build job, re-runs twice each case that passes on the base branch and failed, and posts one sticky comment from the gate job.

* test(studio): mark the ratchet accuracy check as intentionally branchy for fallow

* test(studio): confirm newly passing edit accuracy cases 2 of 3 before banking them

The shards re-ran only regressions, so one lucky pass of a base-failing case failed the gate as unbanked and was never listed as unstable. Every case whose verdict differs from the base branch, either way, is now re-run twice and judged 2 of 3. A render error fails the gate like an unsettled snapshot, a malformed baseline.json fails the job instead of reading as empty, and the header is 3 lines.

* ci(studio): run the edit accuracy gate for every package the bench builds or imports

* ci(studio): also run the edit accuracy gate for lint, sdk and the Studio and CLI build inputs

* ci(studio): run the edit accuracy gate when a bench package's manifest or exports change

* test(studio): bank the 120 edit accuracy cases that sub-pixel dragging now passes
2026-09-30 23:22:00 +00:00
Miguel Ángel 132b9909fa chore: release v0.8.99 (#4800) v0.8.99 2026-09-30 22:41:49 +00:00
Miguel Ángel ca3a3f6e0b fix(studio): keep sub-pixel position and angle while dragging and on drop (#4766)
* fix(studio): keep sub-pixel position and size while dragging and on drop

* test(studio): expect three-decimal rotation in the manual edits rotation test

* fix(studio): keep resized boxes on whole pixels so a centred element stays put
2026-09-30 21:51:21 +00:00
Miguel Ángel ca54631464 test(studio): measure render drift against a producer frame in the edit accuracy bench (#4764)
* test(studio): measure render drift against a producer frame in the edit accuracy bench

Finds the target's box in a producer-captured frame by its flat colour, with sub-pixel edges from area moments, and compares it with the reloaded preview. The baseline gains the render column from the same full run on main.

* test(studio): mark the pixel box scan as intentionally complex for fallow

* test(studio): regenerate the edit accuracy baseline with render drift on main

Same full run on main 14e87005d as #4760's baseline, with the render column: render passes 618/660, 70/660 pass, 314/660 pass everything but smoothness.

* test(studio): fail only render drift when the producer cannot render a case

A producer capture failure used to turn the case into a harness error that wiped every accuracy metric. It now sets render to null with renderError, fails render alone, and is counted in the table.

* test(studio): shorten the render drift box comment to two lines
2026-09-30 21:01:40 +00:00
Miguel Ángel 299670e210 fix: stop two merge-queue flakes in the history prune and timeline tests (#4786)
* test(studio-server): history prune tests allow a file time a moment past Date.now()

* ci: temporary probe for the history prune test flake

* fix(studio): timeline scroll-end timer no longer fires after unmount

* ci: remove the temporary history prune probe

* test(studio): check the virtualizer teardown with a spy and a 200 ms wait
2026-09-30 20:51:39 +00:00
Leonel Rivas 7b0ec69704 fix(core): pair a staged scene copy's video with its own render frame (#4791)
Page-side shader transitions clone the outgoing and incoming scenes,
ids included, into staging layers. Color grading grades the copied
<video> too, and findInjectedRenderFrame looked its frame up with
getElementById, which returns the live scene's <img>. The live and the
copied grading entries then both kept their canvas right after that one
<img>, moving each other on every mutation, and the loop blocked the
page: a graded <video> inside a transition stopped the render at the
transition's first frame.

Look through the element's following siblings first, where the injector
puts the frame, and fall back to the id lookup only when it isn't there.
2026-09-30 20:03:54 +00:00
Miguel Ángel f1c1f1d69c fix(parsers): moving a clip on the timeline moves its inner animations too (#4761)
* fix(parsers): moving a clip on the timeline moves its inner animations too

* fix(parsers): a timeline move keeps chained and partly known tweens where they belong

* fix(parsers): moving a clip leaves chained outside tweens and template aliases in place

* fix(studio): a clip timing edit syncs its animations once, not twice

* fix(studio): a timing edit falls back to the server when the SDK wrote no animation change

* fix(studio): the SDK sync check reads scripts inside a template too

* fix(studio): the SDK sync check finds template scripts through the DOM walker

* fix(core): a clip retime writes only its own timed tweens, never outside ones

* style(core): format the clip tween import
2026-09-30 20:03:48 +00:00
Miguel Ángel 567525a8c2 fix: readers never see an empty or half-written project file (#4777)
* fix: readers never see an empty or half-written project file

* test: pin the create routes and block install to whole-file writes

* fix(sdk): a write through a link to a missing file creates that file

* fix: saves through a linked folder reach the real file, atomic writes handle long names

* test(sdk): check for the stray file, not the folder listing

* fix(core): creates work on Windows FAT drives, and links through folders resolve as the system does

* fix: agree the helper move in the deletion check, and match temp paths without a path regex

* test: check POSIX file modes only off Windows
2026-09-30 19:17:59 +00:00
Miguel Ángel 76c634dfa5 fix(studio): resizing a cropped element keeps the same part of it in view (#4743)
* fix(studio): resizing a cropped element keeps the same part of it in view

Rebased onto main as one commit (was a0a86cc1..080dfecf). Merged with the crop
release rule from main: a drag that ends where it started saves nothing.

* fix(studio): the crop follows each axis GSAP does not tween, while dragging and once saved

* fix(studio): the live-tween check reads every timeline, not only the first after a soft reload
2026-09-30 18:44:04 +00:00
Miguel Ángel 04b01342cf chore: release v0.8.98 (#4788) v0.8.98 2026-09-30 18:43:57 +00:00
Miguel Ángel 5b79038ca4 feat(studio): hosts can add their own items to the clip right-click menu (#4784)
* feat(studio): hosts can add their own items to the clip right-click menu

* refactor(studio): let the clip menu item names carry their meaning

* fix(studio): a clip menu item that moves the focus keeps it after the menu closes

* test(studio): the clip menu focus hand-off holds under StrictMode

* docs(studio): state the menu focus rule without history
2026-09-30 18:07:24 +00:00
Miguel Ángel 8d537dea9f fix(studio): save moves in GSAP-free files without a 2 s wait for a warm parse (#4770)
* fix(studio): save moves in GSAP-free files without waiting 600 ms for a warm parse

* fix(studio): read a GSAP-free file once instead of re-fetching it as a cold parse
2026-09-30 17:28:59 +00:00
Miguel Ángel 0f390167df perf(studio): stop sorting the font list while the font picker is closed (#4769)
* perf(studio): stop sorting the font list while the font picker is closed

* test(studio): keep the font list test off the network and clean up on failure

* test(studio): stub the font APIs for every font field test and clear the head after each
2026-09-30 17:28:46 +00:00
Miguel Ángel 0c6f5fd80e test(studio): an edit accuracy benchmark for manual editing (#4760)
* test(studio): start an edit accuracy benchmark for manual editing (work in progress)

* test(studio): an edit accuracy benchmark for manual editing

Drives real pointer and keyboard gestures in the built CLI Studio on generated one-element projects and scores tracking, drop jump, reload drift, undo fidelity and smoothness in composition px.

* test(studio): move render drift out of the first benchmark PR

Render drift against a producer frame ships in the stacked follow-up so this PR stays near the size budget.

* test(studio): keep the edit accuracy bench clean under fallow

Builds the grid as a product of its axes, drops unused exports and marks the harness functions that fallow scores as complex.

* test(studio): report edit accuracy with the Studio and bench commits it measured

Moves scoring and the report files into report.mjs, stamps both commits at start, counts undo byte failures apart from the worst box, and survives Studio swapping the preview iframe mid-read.

* test(studio): bank the edit accuracy baseline measured on main

660 cases on main ae914334f: 0 pass all six checks, 304 pass everything but smoothness. Five cases that hit a harness error in the full run were rerun and spliced in.

* test(studio): state the edit accuracy build prerequisite in the runner header

* test(studio): keep the edit accuracy baseline one line per case under the formatter

* test(studio): store edit accuracy baseline values rounded up

Rounding to the nearest 0.01 stored seven 0.502 px tracking results as 0.5, which reads as a pass at the 0.5 px limit.

* test(studio): score edit smoothness on dropped frames and main-thread work per frame

* test(studio): measure edit accuracy on a still preview and score press jump

Snapshots wait for the preview frames and the box to hold still for 1 s, so Studio's asynchronous reload after a save is measured, not raced. A preview that never settles fails the metrics it feeds instead of erroring.

Adds press jump (box at press against the box before it, 0.5 px). Smoothness counts only dropped frames beyond a blank-page control driven the same way in the same Chrome; unknown trace data fails smoothness alone.

The runner takes the suite lock per chunk of 8 cases (--lock), reads the port Studio announces, keeps undo history and tmp projects inside the case dir and removes them on a signal, and stamps the CLI tree's commit and bundle hash.

* test(studio): stamp the edit accuracy build with the Studio bundle as well as the CLI

The CLI bundle hashed the same for two different Studio builds; Studio's index.html names its hashed assets, so the stamp now covers it.

* test(studio): regenerate the edit accuracy baseline on main after the settle fix

Full grid on main 14e87005d with the settle fix, press jump and the smoothness control: 70/660 pass, 314/660 pass everything but smoothness, 0 harness errors, 0 unsettled. Scored without render, which #4764 adds.

* test(studio): wait for each edit accuracy undo write before sending redo

Undo and redo each wait up to 15 s for their own write, like the settle; redo is sent only once undo landed, and a write that never lands fails undo with undoTimeout instead of scoring an untested redo. The reload waits for the files to hold still. The headline is now the accuracy count, with smoothness on its own line against the control. The settle compares each read with the window's first read, and a CLI outside a git tree no longer stops the run.
2026-09-30 17:07:51 +00:00
Miguel Ángel d572fe3baf ci: install a pinned static ffmpeg on linux instead of apt (#4782)
* ci: install a pinned static ffmpeg on linux instead of apt

* ci: run the jobs that use a shared action when that action changes

* ci: drop the ffmpeg cache so the install is bounded by the download alone

* ci: cap ffmpeg download retries so a Retry-After header cannot stretch the install
2026-09-30 17:07:32 +00:00
Miguel Ángel d1a5a05609 fix(studio): a click after an empty-canvas press selects on the first try (#4759)
* fix(studio): a click after an empty-canvas press selects on the first try

* fix(studio): a shift press outside the box no longer sets a click guard that never fires
2026-09-30 14:47:35 +00:00
Miguel Ángel 9ae1e3c842 chore: release v0.8.97 (#4775) v0.8.97 2026-09-30 14:08:38 +00:00
Miguel Ángel 7734697872 fix(studio): a copied clip keeps its look and motion, and a duplicated section keeps its name (#4756)
* fix(studio): a copied clip keeps its look and motion, and a duplicated section keeps its name

* fix(studio): a copy's tweens start where their originals do and stay in the timeline's scope

* fix(studio): a copy takes only tweens it can place exactly, and never moves the film's own

* fix(studio): a copy leaves out a tween that sits inside a callback or a guard

* fix(studio): a copy leaves out a set that runs only inside an if or an else

* chore(studio): keep the copy change under the comment ratchet
2026-09-30 12:58:02 +00:00
Miguel Ángel 14e87005d9 fix(studio): name a repeated section by its authored id in the Design panel (#4768)
* fix(studio): name a repeated section by its authored id in the Design panel

* fix(studio): the timeline's page-read fallback also names a repeated section by its authored id
2026-09-30 12:48:00 +00:00
Miguel Ángel ae8d1780ff fix(studio): undoing an agent's build takes its sections off the timeline too (#4757)
* fix(studio): undoing an agent's build takes its sections off the timeline too

* fix(studio): a reloaded preview's timeline is read from the new document, sections included

* fix(studio): a section still in the preview stays on the timeline, and trims are unchanged
2026-09-30 11:27:13 +00:00
Miguel Ángel 9a27b9f934 feat(studio): select rightward takes crossing clips, and a host can keep only Select and Split (#4755)
* feat(studio): a host can keep only Select and Split in the timeline tool menu

* refactor(studio): drop the prop comment the name already carries

* feat(studio): select rightward includes clips crossing the playhead
2026-09-30 09:27:28 +00:00
Miguel Ángel 18a1b226c5 feat(studio): a popover can point at its trigger with an optional arrow (#4758)
* feat(studio): a popover can point at its trigger with an optional arrow

* fix(studio): keep a side arrow off the popover corner

* refactor(studio): name the side arrow corner clearance instead of commenting it

* refactor(studio): the arrow corner clearance applies on every side
2026-09-30 09:07:15 +00:00
Miguel Ángel be5beaee61 fix(studio): font faces a host saves keep their own weight and style (#4753)
* fix(studio): a family's saved font faces keep their own weight and style

* fix(studio): keep the font face change within the comment ratchet

* fix(studio): a saved font face cannot break the page, and only weights a browser keeps are written
2026-09-30 08:57:58 +00:00
Miguel Ángel 84811c4329 fix(studio): shift+click on a group member removes it for good (#4754)
* fix(studio): shift+click on a group member removes it for good

Removing a member with shift+click shrank the canvas group, but the timeline kept the old set, and the timeline syncs its set back onto the canvas, so the member came back about 70 ms later. A one-element marquee over a live group had the same fault. The canvas now publishes the new set whenever the caller states the whole selection (a shift toggle or a marquee), while a late single primary inside the live set still keeps it.

* test(studio): a shrinking selection keeps the remaining member as the anchor
2026-09-30 07:08:14 +00:00
Miguel Ángel ae914334f2 feat(studio): let a host make the timeline read-only while seek, scroll and zoom stay live (#4749)
* feat(studio): let a host make the timeline read-only while seek, scroll and zoom stay live

Timeline takes readOnly and onReadOnlyPress. In read-only the subtree sees no edit callbacks, every clip is locked, and each refused press (clip drag, clip/lane/keyframe menu, drop, eye, razor, beat dot, FX) reports once through onReadOnlyPress.

* refactor(studio): route read-only refusals through one helper and share the keyframed test fixture

Keeps the provider hook under the complexity limit, drops the comments the ratchet counted, and reuses the ruler test's keyframed card instead of copying it. The provider keeps one tree shape in both modes so toggling read-only keeps scroll and zoom.

* fix(studio): switching the timeline to read-only drops edits already in flight

On the switch into read-only the timeline cancels an active clip drag or resize, beat drag and keyframe retime without committing, and closes the clip, gap and keyframe menus. A keyframe-diamond drag and an automation-lane point drag or double-click now report through onReadOnlyPress too, and the refusal stays one stable function however the host writes its callback.
2026-09-30 05:32:47 +00:00
Miguel Ángel 5461e0ab03 chore: release v0.8.96 (#4752) v0.8.96 2026-09-30 05:17:09 +00:00
Miguel Ángel 3f8b46cc1e fix(studio): shift+drag moves a selection along one axis instead of deselecting it (#4747)
* fix(studio): shift+drag moves a selection along one axis instead of deselecting it

* fix(studio): a tap on a selection puts the preview back and a travelled drag never toggles

* test(studio): a cancelled drag returns the preview to its start
2026-09-30 05:07:14 +00:00
Miguel Ángel 67327a8b25 fix(studio): a crop drag that ends where it started saves nothing (#4745)
* fix(studio): a crop drag that ends where it started saves nothing

* fix(studio): a crop pan back to its start saves nothing, and a release saves where it let go

* test(studio): a first crop released before its last render survives deselect
2026-09-30 04:17:43 +00:00
Miguel Ángel 4825f79294 fix(studio): an element placed by a stylesheet translate stays put after a move or resize (#4734)
* fix(studio): an element placed by a stylesheet translate stays put after a drag or resize

* fix(studio): an unset computed translate reads as nothing underneath

* fix(studio): an element on the old offset channel keeps its folded translate masked too

* test(studio): pin that a drag GSAP does not own leaves its translate unmasked

* test(studio): the unmasked translate still carries the offset channel
2026-09-30 03:51:21 +00:00
Miguel Ángel f5a348e92d chore: release v0.8.95 (#4751) v0.8.95 2026-09-30 03:47:02 +00:00
Miguel Ángel c3f7066bf3 feat(studio): draw the empty lane below the last track as a drop target and tighten the top gap (#4741) 2026-09-30 03:14:08 +00:00
Miguel Ángel 2fc5a37fce fix(core): a video's sound plays at its boosted volume in the preview (#4746)
* fix(core): a video's sound plays at its boosted volume in the preview

A <video> with data-volume above 1 now plays through the same Web Audio gain as <audio>, so the preview matches the export. Videos at or below 1 keep their native output.

* style(core): keep the runtime's comment share at its base

* fix(core): a boosted video hidden after its volume drops goes silent
2026-09-30 03:07:21 +00:00
Miguel Ángel efd3c51ac9 fix(studio): undo puts a resized, rotated or moved element back on screen (#4740)
* fix(studio): undo puts a resized, rotated or moved element back on screen

* fix(studio): undo no longer drifts an element with a Studio offset and a GSAP tween

A soft reload killed the old timeline but kept its children, and the runtime seeks that captured timeline until the rebind swaps it. The old tween painted its transform after the authored-transform restore, and the new tween folded it into its start. Clear the timeline before killing it. The restore also covers SVG elements now, and the save path has a test.
2026-09-30 02:18:34 +00:00
Miguel Ángel 7a1869d921 fix(studio): a timeline scrubs when its host omits sessionEpoch (#4742)
* fix(studio): a timeline scrubs when its host omits sessionEpoch

* fix(studio): drop the added comment for the comment ratchet
2026-09-30 02:02:46 +00:00
Miguel Ángel daa44fcd75 chore: release v0.8.94 (#4744) v0.8.94 2026-09-29 17:41:30 -07:00
Miguel Ángel 5470d5dea1 fix(studio): selecting words while editing text keeps the edit open (#4738) 2026-09-29 17:21:04 -07:00
Miguel Ángel 3e4ccd9567 fix(studio): a corner drag on a small element resizes it instead of starting a crop (#4730)
* fix(studio): a corner drag on a small element resizes it instead of starting a crop

* refactor(studio): one crop gesture ending, edge layout in one helper
2026-09-29 17:20:49 -07:00
Miguel Ángel ac754b25d0 feat(studio): a host can turn off body drags and keep the resize, rotate and crop handles (#4732)
* feat(studio): a host can turn off body drags and keep the resize, rotate and crop handles

* fix(studio): drop comments the names already say

* fix(studio): a refused group body press still swallows its trailing click

* fix(studio): a refused body press reaches the host untouched

* test(studio): a refused group press sets no cursor

* fix(studio): body drag turns off only for a host that takes the canvas input

* docs(studio): say allowBodyDrag takes effect with the host canvas input

* docs(studio): keep the Enter note on the canvas input doc

* docs(studio): shorten the canvas input doc line
2026-09-29 17:20:35 -07:00