Commit Graph
5091 Commits
Author SHA1 Message Date
Miguel Ángel d05344b3ee fix(studio): a reload requested while a canvas save is in flight loads again once it lands (#4874)
* fix(studio): a reload requested while a canvas save is in flight loads again once it lands

* test(studio): type the late-reload drag helper for every edit kind
2026-10-01 13:56:37 -07:00
Miguel Ángel 30c55694cb fix(studio): a first animation in a sub-composition file runs from inside its template (#4806) 2026-10-01 13:40:55 -07:00
Miguel Ángel 78e849235a fix(core): a composition made only of a set shows it at a seek in the preview (#4865)
* fix(core): a composition made only of a set shows it at a seek in the preview

* chore(core): keep the comment share of the runtime init file level

* test(studio): bank the four resize-hold rows the preview now gets right
2026-10-01 12:08:42 -07:00
Miguel Ángel c72b735a8c fix: a save's temp file no longer reaches Studio as an outside change (#4857)
* fix: a save's temp file no longer reaches Studio as an outside change

* refactor: the project file walk and its tests carry no new duplication or complexity

* test(studio): the edit accuracy gate judges the two nudge sequences again
2026-10-01 11:48:00 -07:00
Miguel Ángel 89704189d8 chore: release v0.8.106 (#4873) 2026-10-01 11:46:50 -07:00
Miguel Ángel 57da627e76 fix(studio): a hand edit after a preview reload draws on the element now on screen (#4856)
* fix(studio): a hand edit after a preview reload draws on the element now on screen

* fix(studio): a preview reload waits for the gesture on screen to end

* fix(studio): a marquee after a reload selects from the preview on screen

* fix(studio): a reload requested before a canvas save loads again once the save lands
2026-10-01 11:32:06 -07:00
Miguel Ángel 44138931ce perf(studio): track the motion path only while a path or create ring is drawn (#4795) 2026-10-01 11:14:55 -07:00
Miguel Ángel 59bbe7f098 feat(studio-server): a write receipt names the version the write replaced (#4864)
* feat(studio-server): a write receipt names the version the write replaced

* test(studio-server): route receipts carry the version each write replaced

* refactor(studio-server): the receipt's from field needs no comment
2026-10-01 11:13:20 -07:00
Miguel Ángel a9929b60ee fix(studio): the preview serves a same-size edit written right after the last one (#4792)
* fix(studio): the preview serves a same-size edit written right after the last one

* chore(studio): keep the comment share and complexity gates green

* refactor(studio): keep the project signature doc comment on the function it describes

* fix(studio): the preview re-reads files written in the last two seconds, whoever wrote them
2026-10-01 11:10:58 -07:00
Miguel Ángel ce76be50c7 feat(studio): paste and duplicate can keep a clip at its own time (#4859)
* feat(studio): paste and duplicate can keep a clip's own time, on the next free track

* fix(studio): an in-place paste uses the copied time only in the composition it was copied in

* test(studio): an in-place duplicate inside a sub-composition keeps its local time

* refactor(studio): the paste anchor is its own function, and the in-place tests share their setup

* refactor(studio): paste names its undo step and note in one place, and two paste tests share their steps
2026-10-01 10:19:50 -07:00
Miguel Ángel e666a8a2a3 feat(studio): the code editor follows studio's light and dark theme (#4862)
* feat(studio): the code editor follows studio's light and dark theme

Replaces One Dark with a Studio editor theme whose colours are theme.css variables: eight syntax role tokens (One Light and One Dark hues held at 4.5:1), editor surfaces from Studio's roles, all measured by the contrast ratchet.

* test(studio): the code editor theme takes every colour from a theme.css token

* fix(studio): code stays at 4.5:1 when a selection or search match sits on the active line

Syntax tokens are tuned for the selection over the active line, which the ratchet now measures; matches are outlined rather than stacking another wash; the search panel's pressed button and special characters follow the theme; unused tooltip rules go.

* fix(studio): code matches carry no background, and types read apart from constants

Search and selection matches are outlined only, so CodeMirror's lime match wash and stacked washes are gone; type and constant tokens split by hue in both themes.

* fix(studio): the current search match reads apart by width, not colour alone

A 2px accent outline on the current match and a muted 1px outline on other copies of the selected word.
2026-10-01 10:13:48 -07:00
Miguel Ángel 9c19327e38 test(studio): catch teleports and multi-step edit bugs in the edit accuracy bench (#4801)
* test(studio): catch teleports and multi-step edit bugs in the edit accuracy bench

* test(studio): share the edit accuracy case harness between single gestures and sequences

* test(studio): keep edit accuracy helpers private unless shared

* test(studio): score plain-CSS edits on pages that load GSAP for other elements

* test(studio): add an idle GSAP axis value so plain-CSS edits are scored on GSAP pages

* test(studio): report a slow undo save as late with its latency, apart from a lost one

* test(studio): sample the gap between back-to-back drags and allow one frame of lag

* test(studio): read the edge from the element's fractional size, so a crop edge is exact

* test(studio): keep waiting for a late save until an undone file stays put for 3 s

* ci(studio): run the edit accuracy grid in 12 shards to stay near 25 minutes each

* test(studio): read edit bench frames just before paint, so unpainted states never count

* test(studio): scope the edit bench frame sampler so page globals cannot clash

* test(studio): let a lagging box catch up the pointer travel it owes, so a held frame is no jump

* test(studio): record every save a sequence makes, even two that land 10 ms apart

* test(studio): end a drag's teleport scoring at its pointer-up, not at the next element's press

* test(studio): bank for each edit accuracy case a run that agrees with the gate's verdict

* test(studio): fail the edit accuracy gate on a teleport or an unmeasured drag

* test(studio): fail the edit accuracy gate when a text edit is not saved or shown

* test(studio): measure but do not gate the edit accuracy cases a known Studio race flips

* test(studio): bank the edit accuracy cases that newly pass with the teleport metric

* test(studio): quarantine the two nudge sequences whose redo write sits at the 2 s limit

* style(studio): cut three bench comments to one line each
2026-10-01 09:42:05 -07:00
Miguel Ángel d4176c4ce6 fix(studio): undo that reloads the preview keeps the timeline clips on screen (#4790)
* fix(studio): undo that reloads the preview keeps the timeline clips on screen

* chore(studio): keep the comment share and complexity gates green
2026-10-01 09:32:53 -07:00
Miguel Ángel 9f4d9e64b2 feat(studio): a light and dark theme toggle in the header that remembers the choice (#4839)
* feat(studio): a light and dark theme toggle in the header that remembers the choice

The sun and moon button flips Studio between Graphite light and dark with a
circular reveal, saves the choice, and follows the system until one is made.

* fix(studio): studio opens light the first time, then keeps the saved theme

* fix(studio): drop the theme field's comment, the theme owner states the rule

* fix(studio): the theme toggle shows only in studio's own app and follows the theme on screen

A host that embeds StudioApp gets no toggle; the button reads the document's theme, the reveal drops the default crossfade, and reduced motion skips it.

* fix(studio): the theme reveal leaves other view transitions alone; one default for the toggle

The crossfade rule applies only while the toggle's own transition runs, StudioApp passes the header's single default through, and the reveal's reduced-motion and keyboard paths are tested.

* fix(studio): a second theme click mid-reveal keeps the crossfade off

Only the latest reveal clears the document marker; tests pin the marker while a reveal runs and the circle's radius.

* refactor(studio): the theme toggle shares its icon props and reads as plain functions

Simplify pass: one icon props object for the sun and moon, no useCallback around a handler nothing memoises, cannotReveal named for what it answers, the test reuses the theme reader.

* fix(studio): studio's own app turns the theme toggle on through context; tests parse index.html

App.tsx stays at main's size: main.tsx provides ShowThemeToggle and the header reads it, default off for hosts. The boot-script tests read index.html through DOMParser instead of an HTML regex.
2026-10-01 08:44:57 -07:00
Miguel Ángel b6c879c4e9 fix(studio-server): a folder rename past an unreadable folder or file answers ok (#4860)
* fix(studio-server): a folder rename past an unreadable folder or file answers ok

* fix(studio-server): a rename skips only folders and files it may not read

* fix(studio-server): the rename's skip helpers say it by name

* test(studio-server): the rename tests share one request
2026-10-01 08:41:39 -07:00
Miguel Ángel 8593b01a6d perf(studio): stop font list work from landing in drag frames (#4794)
* perf(studio): load the font lists when the font picker first opens

Selecting an element no longer fetches and dedupes the 1.7k-name Google font list, so the response cannot land inside a drag frame. The lists are fetched once per session on the first open.

* test(studio): expect the font lists to load on the first picker open

* perf(studio): load the font lists at session start and process them when input is idle

* style(studio): drop a comment the font list names already carry

* perf(studio): process the font lists in idle slices of at most 3 ms

* perf(studio): join the font lists without iterating them when they arrive
2026-10-01 08:35:28 -07:00
Miguel Ángel 07af05c816 feat(studio): fade handles ride the fade's end and the waveform shows the fade (#4844)
* feat(studio): fade handles ride the fade's end and the waveform shows the fade

* refactor(studio): read fade handle keys from one lookup

* fix(studio): fade handle keys stay with the handle and snap past the clip's own edges

* fix(studio): Cmd shortcuts keep working on a focused slider and fade boxes stay on their tabs

* fix(studio): keep a fade handle while it is in use and restore the viewport gate's Chrome path

* fix(studio): a fade's draft clears with its own save and grow drags never shrink it

* fix(studio): saved fades reach the timeline store so the handle stays where it was dropped

* fix(studio): a fade longer than its clip starts a drag or key from the clip's end

* fix(studio): fade comments state the rule, not its history
2026-10-01 08:15:10 -07:00
Miguel Ángel bc57e282fd chore: release v0.8.105 (#4858) 2026-10-01 07:33:11 -07:00
Miguel Ángel 658f22bb7f feat(studio): graphite colour system with a light theme that passes contrast (#4836)
* feat(studio): graphite colour system with a light theme that passes contrast

Studio's tokens take the Graphite values: a lifted dark, a light set behind
data-theme="paper", the player dark in both. Every text role clears 4.5:1 and
every icon and ring 3:1 on the fills it sits on, held by the contrast ratchet.

* fix(studio): every studio surface follows the light theme and passes contrast

The dock no longer inherits dockview's forced dark scheme, popovers sit on the
raised surface, stock white, red and amber text map to roles, the keyframe
diamonds and track headers use roles, and the contrast ratchet measures every
pair in both themes (150 pairs).

* fix(studio): stock white, red and amber stay colours; studio text and fills use roles

Round-two review: white, red and amber are colour content as well as text, so
they keep Tailwind's values and hosts keep their palettes. Text, toasts, the
feedback card, the FX popover, destructive buttons, category labels and the dock
take Graphite roles instead; the contrast gate rejects an unparseable colour.

* fix(studio): every light-theme ink, icon, dot and focus ring holds its contrast

Stock white, red, amber and file-type hex left in Studio chrome now use roles or a light-dark pair; the file tree icons carry a contrast test.

* chore(studio): drop two comments the names already carry

* fix(studio): red marks, teal outlines and colour curves hold contrast in both themes

Danger graphics use the danger ink, file tree outlines use the accent ink, the colour-curve well stays dark like the scopes, and six timeline token pins come back.

* fix(studio): invalid readouts, the curve focus ring and editor dots stay distinct in both themes

The invalid readout border is dashed as well as red, the curve graph's ring sits inside its dark well, the radius dots and ease handle centre follow the theme.

* refactor(studio): ease and radius editors use colour classes; the dashed invalid border has a test

Simplify pass: the ease editor's inline accent styles become stroke and fill classes, the radius dot class is computed once, the ratio check is one expression, and the alias table is named for what it holds.
2026-10-01 07:21:05 -07:00
Miguel Ángel 3d8325e7a4 fix(studio): a pasted element keeps the look its id gave the original (#4852)
* fix(studio): a pasted element keeps the look its id gave the original

* fix(studio): drop a comment the parameter name already says
2026-10-01 07:12:37 -07:00
Miguel Ángel c20934a720 fix(studio): the timeline viewport gate judges p95 over every measured scroll step (#4847)
* fix(studio): the timeline viewport gate judges p95 over every measured scroll step

* fix(studio): format the pooled p95 lines in the timeline viewport gate

* test(studio): pin the timeline viewport gate's pooled verdict with tests

* refactor(studio): share one Chrome launcher across the timeline and fade-handle gates
2026-10-01 05:51:59 -07:00
Miguel Ángel 0b417cb57a refactor(studio): delete the move writer no Studio path calls (#4837) 2026-10-01 05:46:28 -07:00
Miguel Ángel e90b1a0200 fix(studio): a moved layer holds through seeks and Reset puts back its authored translate (#4840) 2026-10-01 05:28:08 -07:00
Miguel Ángel d0404b450c fix(studio): undo and redo are no longer held behind thumbnail renders (#4842)
* fix(studio): undo and redo are no longer held behind thumbnail renders

* test(studio): composition card tests share one mount, and the bench's 2 s undo rule is tested

* fix(studio): a composition card and its timeline clip share one thumbnail render

* test(studio): bank opening a film at 15 thumbnail requests, one per composition

* fix(studio): composition cards load near the viewport and keep their last frame until the next

* fix(studio): a composition card keeps its last frame under cache pressure, and loads 200 px early
2026-10-01 04:20:14 -07:00
Miguel Ángel 0db5b0a285 chore: release v0.8.104 (#4850) 2026-10-01 03:53:24 -07:00
Miguel Ángel 74f5013210 fix(studio): a rotate without GSAP saves where you let go, nested too (#4802)
* fix(studio): a rotate GSAP does not own saves where you let go, as the element's own CSS rotate

* fix(studio): a rotate keeps a transform-centred element in place

* fix(studio): a plain rotate shows its angle in the panel and turns inline elements too

* fix(studio): a rotate turns a mirrored or stretched centred element with the cursor

* fix(studio): keep the rotate's panel readout within the comment budget after the move landed

* fix(studio): an inline element's turn saves the inline-block it was drawn with

The overlay passes the rotate target it read at press into the commit, so the
save draws and patches exactly what the draft did instead of re-reading an
element the draft already made inline-block. The GSAP-aware commit also routes
on that press-time decision.

* test(studio): bank the 27 rotate cases a GSAP-free rotate now passes

baseline.json is the edit-accuracy-gate artifact of the CI run at cb75cc37,
copied byte for byte: 530 to 557 passing, all rotate-none cases, none
regressed.

* fix(studio): a word an older Studio turned keeps its inline-block and whole angle on a new rotate

Clearing the legacy rotation marks puts the element back to inline and drops
their turn, so the save reads the target again instead of using the one the
press read before the clear. A test also pins that a turn the press drew as CSS
stays on the CSS writer.

* test(studio): bank the edit accuracy baseline from the rebased rotate run
2026-10-01 03:38:30 -07:00
Miguel Ángel bb0d076c14 fix(studio): Escape keeps the text you typed in an in-place edit (#4841) 2026-10-01 02:59:24 -07:00
Miguel Ángel 2eb1919103 fix(studio): a move keeps the route it chose at press through drop and save (#4834)
* fix(studio): a move keeps the route it chose at press through drop and save

* fix(studio): a refused move drops the transform GSAP folded it into, so the layer goes back

* fix(studio): a group refuses a GSAP takeover before writing any member, and only a real fold refuses
2026-10-01 02:02:11 -07:00
Miguel Ángel 3e011b7e5f fix(studio): resize an element without GSAP by its own width, height and translate (#4805)
* fix(studio): resize an element without GSAP by its own width, height and translate

A corner resize of an element GSAP does not size or position now saves plain CSS:
its inline width and height, the scaled crop, and the translate that keeps its
centre where it was, in one save and one undo step, with no animation read and
no preview reload. It used to write a GSAP script into a file that had none, and
the box snapped back to its old size on release.

The anchor that keeps the centre planted is the same plain px translate a move
writes, kept to a thousandth of a pixel, and a percent translate is held as px
from the press so a growing box cannot drag it along on the first frame.

* fix(studio): a resize leaves a box to GSAP when GSAP tweens anything the CSS box writer sets

gsapWritesBox now reads its channels from BOX_SIZE_STYLE_PROPS, the writer's own
property set, instead of width and height alone. A host-level test pins the
GSAP-free resize route through useDomGeometryCommit.

* fix(studio): a GSAP scaleX or scaleY tween also keeps a resize on the GSAP route

They are GSAP's names for parts of the one CSS scale the box writer sets.

* test(studio): bank the edit accuracy cases a plain-CSS resize now passes

baseline.json is the edit-accuracy-gate artifact of the CI run at 5ae51f1d,
copied byte for byte: 494 to 530 passing, the 36 resize cases without GSAP,
none regressed.
2026-10-01 00:57:46 -07:00
Miguel Ángel 86a7e8cc75 chore: release v0.8.103 (#4838) 2026-10-01 00:15:00 -07:00
Miguel Ángel e3ac225a9c fix(studio): fade handles show a real tooltip and a 24 px target (#4819)
* fix(studio): fade handles show a real tooltip and a 24 px target

A sound clip's fade-in and fade-out dots now open Studio's tooltip
("Fade in 0.3 s") instead of a native title the clip's own title
competed with, take a 24 px hit box, and paint an opaque white dot that
reads on the audio clip in both timeline themes.

* fix(studio): fade handles sit at the clip's top corners, above its content

The fade-in and fade-out dots now pin to the clip's top-left and top-right
corners instead of the end of each fade, paint above the waveform and
thumbnail layers, and share a short clip's width without overlapping. The
fade ramps are clipped to the clip's rounded corners, and the tooltip
uses the same seconds format as the slider's value text.

* fix(studio): fade dots keep their 10 px size on a very narrow clip

On a clip narrower than two fade hit boxes the flex row squeezed each dot
below 10 px. The dot no longer shrinks and no longer takes the pointer, a
browser check measures its box on a 0.1 s clip in CI, and two drag test
comments now describe the corner handles.

* fix(studio): the Escape key cancels a fade drag wherever keyboard focus is

A press on a fade handle never focuses it, so Escape only reached the
handle's own key handler when something else had focused it, and the
release then saved the dragged fade. The drag now listens for Escape on
the window for as long as it lasts, restores the fade and saves nothing.

* test(studio): trim the fade handle browser check header to what the code cannot say
2026-09-30 23:55:14 -07:00
Miguel Ángel 7880d41bc4 fix(studio): a layer with a min(), max() or clamp() translate moves from where it really is (#4832)
* fix(studio): a translate written with min(), max() or clamp() moves without a jump at press

* fix(studio): refuse out loud a translate Studio can't read, and resolve percent against the transform box

* fix(studio): one refusal per held arrow key, and the panel reuses the offset it already read
2026-09-30 23:38:20 -07:00
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) 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) 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) 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) 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