Keep the playhead where it is.

That's the promise I want from hot reload in a video editor.

If I'm adjusting an element halfway through a video, replacing its styles should let me inspect it at that time. Restarting the player throws away the state that made the edit useful. (The browser has refreshed successfully, and I have acquired some scrubbing to do.)

The folder-based editor gives me reusable pieces and a Go worker to build their descriptions. They need to stay useful while I edit. Moving a banner should let me carry on looking at the banner.

Three parts could start over. The watcher notices a file change, the builder produces a new description, and the player renders it. Only the player knows where I'm watching and which state I'd be irritated to lose.

A piece needs a small agreement with the player

Take the browser clip that starts at thirty seconds. I'm looking at it at thirty-five, and I change the border around its window. That's a style edit. The content and timing haven't moved.

Here the component needs configuration and a time to render at, and the player gives it five seconds of local time here because it knows where the clip belongs in the video. The browser piece can update its appearance without deciding to put the whole video back at zero.

Classify the edit before rebuilding the view

This is why web components appealed to me. Each piece has an interface for the application. Its internal markup can be its own concern. The agreement needs to cover timing and updates as well as putting something on the page.

A terminal display and a browser window can draw different things while accepting the same timing and configuration, so I can work on a new piece without teaching the outer player every detail of it.

That is the pluggable part I'm interested in. I still need to select the implementation and check its configuration. But its drawing code has somewhere to live. I can try an idea in one component instead of making the main player understand another little application. A terminal, another window, whatever I decide this video needs.... It's my video, so the list is as long as I like.

A style change and a new video configuration don't make the same parts of the view out of date, so for that border edit rebuilding the animation styles can be enough. Changing the browser window's content asks for different work. Here's one possible arrangement:

Changed dataRequested render work
Element stylesCSS
Window markupHTML, with media refreshed by the HTML path
Element configurationHTML, media and CSS
Animation presetsCSS and HTML
Video-level configuration or element countFull render

These names tell the player which stages to run again. They're deliberately coarse, so a request for HTML can rebuild several DOM nodes at once, and the choice I'm making is between the markup and the animation styles.

The single “changed” flag is tempting because it is easy to pass around. The player would have to guess what I meant by it. Naming the work that's out of date gives it something to act on. If a value affects markup and animation, refreshing only the markup leaves me looking at two versions of my edit at once.

One comparison uses JSON.stringify. Here is a small styles example:

function changedStyles(before, after) {
  return JSON.stringify(before.css) !== JSON.stringify(after.css);
}

console.log(changedStyles(
  {css: {colour: "blue"}},
  {css: {colour: "green"}}
)); // true

That is convenient when one builder produces both versions with a stable representation. It is not semantic equality for arbitrary JavaScript objects. Different property insertion orders can give different strings, and values omitted by JSON can disappear from the comparison entirely.

I don't especially want to write an all-purpose object comparison for this.

The builder is mine. I can restrict its output to JSON data and choose a stable representation. That's a choice I can check, with much less ambition than deciding when any two JavaScript objects mean the same thing.

Keep state through the replacement

I keep calling it “reload”, but that name is hiding two different jobs. Changing configuration or styles is different from changing the JavaScript that implements the component.

Late loading can register a new custom element, but it doesn't replace one already registered. The registration and module-cache limits still apply here.

For a style edit, I can keep the implementation and update its view. New code needs to take over. What happens to the old instance's state? I'm still experimenting with how these pieces are assembled. I haven't settled how to replace arbitrary component code yet.

Keeping the update interface small makes those experiments easier to contain. The outer player can ask a piece to accept configuration and render at a given time. Then I have something to preserve while changing how the piece does its work.

Time is only one part of the editing session. Was I playing or paused, what happens to audio, and what if I cut the video shorter than the saved time? Those need their own choices, and the handoff below preserves only the playback position.

A helper can save the time before rebuilding the view. The handoff fits in three lines of pseudocode:

const time = scene.getTime();
await renderVideo(scopes);
window.setTime(time);

Read the current scene time, perform the selected work, then seek the rebuilt view to that time.

Updating the existing scene in place may preserve its time already. Replacing the scene makes that assumption unsafe. The saved value needs to live outside the thing being replaced.

I can keep that saved time while changing how the renderer works. Even if it replaces the whole scene, I've got the position to give back to the new one. I don't have to remember which parts of the old scene it happens to keep.

After all that rendering, the thing I wanted to keep is one number.

Which state belongs to the editing session, and which belongs to the video? A new style should preserve my position. Selecting a different video may deserve a fresh position. Treating both as “reload” is convenient for an implementation and a bit blunt for an editor.

The Go worker's file watching can trigger a build of the affected video's description. One option for the player is to poll for that description in development mode. I can consider how quickly it should check separately from deciding which state survives an update.

Then there's the next edit arriving while this one is still rendering. A slower old render can finish last and put everything back. Running updates one at a time, or giving them revision numbers, gives me an order to enforce. Keeping the playhead is rather less helpful if I keep returning it to yesterday's version.

Back at thirty-five seconds, the browser window should have its new border and still be five seconds into its own clip. That's the result I'm aiming for. I can try another style, or work on another component, and carry on with the edit instead of watching the introduction again.

This entry came over from blog.haddon.me. I’ve tidied up the grammar and flow, bringing it closer to how I write now. I used AI to help with the grammar, structure and flow. I have dyslexia, and my writing often comes out as a rambling scrawl, so it can be hard to see what I was actually working on at the time. I think the tidied versions are easier to digest and more interesting to read. The artefacts got the same tidy-up while I was at it.