Skip to content

Studio: Backspace in a text property field deletes the selected design element (silent data loss) #2981

Description

@ajp-productions

Summary

In Studio, pressing Backspace while the caret is inside a text property field deletes the selected design element instead of a character. The first press is consumed correctly by the input; the second one falls through to what appears to be a global delete-element shortcut and removes the entire selected clip from index.html.

This is silent data loss during ordinary typing. It happened twice in a single 10-minute editing session and destroyed two complete scene cards.

Severity

High — normal text editing destroys unrelated markup, and the recovery path (below) additionally rolls back edits the user had already made. There is no visible warning and no obvious undo affordance at the moment it happens.

Steps to reproduce

  1. Open a composition in Studio (npm run dev / hyperframes preview).
  2. Select a design element that contains a text child.
  3. Click into the text property field in the inspector and place the caret at the end.
  4. Press Backspace twice.

Expected: two characters are removed from the text field.

Actual: the first press removes one character; the second deletes the selected design element and all of its children from the document.

Evidence

Studio's own .hyperframes/backup/ trail captures both occurrences. Diffs below are data-hf-id stripped for readability.

Occurrence 1 — 00-48-2700-49-05 (36,691 → 35,349 bytes). Caret was in the #t-repl-kicker text field, mid-word ("This video was made with a "). One backspace removed the whole #t-repl clip:

-      <div id="t-repl" class="clip type" data-start="40.752" data-duration="6.792" data-track-index="2">
-        <div id="t-repl-label" class="label">Replaced by</div>
-        <div id="t-repl-r1" class="repl-row">
-          <div class="repl-name">HeyGen</div>
-          <div class="repl-note">Avatars, voice, video</div>
-        </div>
-        ... (three more rows)
-        <div id="t-repl-kicker">This video was made with a </div>
-      </div>
+

Occurrence 2 — 00-57-40-97100-57-48 (36,711 → 36,293 bytes). Caret was in the #t-end-credit field. The preceding backup shows "...Claude Code an Codex" — the first backspace had correctly deleted the d from and. The next backspace removed the whole #t-end clip:

-      <div id="t-end" class="clip type" data-start="52.072" data-duration="4.528" data-track-index="2">
-        <div id="t-end-1" class="hero">Adobe</div>
-        <div id="t-end-2" class="hero">Cancelled</div>
-        <div id="t-end-credit">Made with HeyGen, HyperFrames, Claude Code an Codex</div>
-      </div>
+

The second case is the clearest: the same key, pressed twice in a row in the same field, is routed to two different handlers.

Secondary effect: recovery discards unrelated edits

After the deletion, the element is restored from a backup — but the restore lands on a state from before the user's recent typing. In occurrence 2, the write at 00-58-10 brought #t-end back while simultaneously reverting #t-end-credit to its original wording, discarding the in-progress edit.

The user-visible symptom is "I made several changes and they all reverted at once," which reads as a separate persistence bug but is downstream of this one. Worth fixing together: a single stray keypress rewinds an entire batch of unrelated edits.

Suggested direction

The global delete/backspace shortcut should bail out when the event target is an <input>, <textarea>, or contenteditable element — or more robustly, when document.activeElement is a text-entry field. A closest('input, textarea, [contenteditable]') guard on the keydown handler would cover the inspector fields.

Environment

  • hyperframes 0.7.90 (latest at time of report)
  • Node v22.17.0, linux x64 (WSL2)
  • Studio via hyperframes preview
  • Composition: single-file index.html, 46 timed elements across 8 tracks, ~36 KB
  • hyperframes check passes with 0 errors before and after; the deletion is not something lint can catch after the fact

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions