Merged
Conversation
Words like <inaudible> or AT&T corrupted the inline-timestamp stream — parsers read them as malformed tags and swallowed the word. Serialization-only: buildWordChunks stays raw for the burn-in renderer, which draws text.
jsonToHTML interpolated raw text into markup that lands in innerHTML — <inaudible> vanished as a bogus tag on round-trip, and a hostile payload in third-party transcript JSON executed (stored XSS). All three interpolation sites now escape & < > "; htmlToJSON reads textContent so the decode is free.
WebKit injects inline styles (font-size etc.) on paste/splits plus style-only wrapper spans with no data-m; both persisted into exports. scrubEditingArtifacts drops them while preserving the two functional styles: line-through (the strikeout/cut model) and display on speaker labels (the Speakers toggle).
New js/transcript-serializer.js emits clean two-space-indented markup for the HTML download, the interactive-transcript dialog, and the export modal's re-timed transcript — replacing raw contenteditable innerHTML (and the old strip-all-classes regex, which wrongly removed the semantic speaker class). Runtime classes and non-functional styles never serialize; text re-escapes from textContent.
…scape names (#410) Three stacked hazards with one failure story: the picker referenced entries by storage.key(i) POSITION, but key order is implementation-defined and shifts whenever any module writes a key (the transcribe prefs do so on every toggle) — so a click could load a different entry than the one listed, including non-transcript keys that parse as JSON and then throw half-way through rendering. Entries are now referenced by their key string (data-key / option value), the JSON.parse is guarded so a corrupted entry can't permanently break the click handler, entries missing hypertranscript/video fields are rejected before rendering, and filenames are escaped so a name containing markup renders as text (self-XSS). e2e coverage: order-shift, corruption, and markup-name cases.
Three silent-loss paths fixed: - jsonToHTML's half-open range filter deleted any word it couldn't bracket — a zero-duration last word failed its own paragraph's range (end == start), and diarizer paragraph times that don't exactly enclose word times orphaned gap and trailing words. Words are now assigned to the last paragraph that has started by the word's start time (words before the first paragraph go to the first), so every word renders exactly once. - htmlToJSON's selector required data-d, which the hyperaudio format treats as optional (caption.js handles its absence) — those spans are now included with duration 0, in both the word extractor and the paragraph-range reader. Unit tests cover the zero-duration/gap/trailing cases; an e2e round-trip test (HTML→JSON→HTML in the real DOM) proves nothing is lost.
The join/split/reflow repairs rewrite text nodes (textContent =, replaceWith), which destroyed the selection anchor — on the debounced sanitise pass the caret jumped from mid-word to the start of the joined/split word. All the passes preserve the transcript's character content, so the caret is now saved as an absolute character offset before the repairs and re-resolved onto the new nodes after — only when the transcript has focus and a rewrite actually happened, so blur and no-op passes never touch the selection.
Self-review of the caret work surfaced three issues: - a non-collapsed SELECTION was collapsed to a caret by the restore — both endpoints are now saved as character offsets and restored as a range - the wordArr re-index keyed on span-COUNT change, so a merge (−1) plus a split (+1) in one pass netted zero and skipped the rebuild, leaving the index holding removed nodes and missing new ones — it now keys on merge/split having fired (reflow keeps the span elements, so it needs no re-index) - normalization now skips while an IME composition is in flight (rewriting text nodes under an active CJK composition breaks it); the skipped pass runs on the next keyup/blur e2e regression tests for all three.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes five bugs from the recent review batch, plus canonical export formatting and caret preservation during editing.
<inaudible>/AT&Tsurvive WebVTT parsing;buildWordChunksstays raw for the burn-in renderer.jsonToHTMLescapes speaker names and word text at all three interpolation sites — closes a stored XSS via third-party transcript JSON and stops<inaudible>vanishing on round-trip.line-through, speakerdisplay).storage.key(i)position (which shifted whenever any module wrote a key — the prefs do so on every toggle);JSON.parseguarded; entries validated before rendering; filenames escaped.jsonToHTMLassigns every word to a paragraph (zero-duration last words, gap and trailing words previously deleted silently);htmlToJSONaccepts spans without the optionaldata-d.js/transcript-serializer.js): the HTML download, interactive-transcript dialog, and export modal's re-timed transcript all emit one-span-per-line, two-space-indented markup withdata-mbeforedata-d— replacing raw contenteditableinnerHTML(and the old strip-all-classes regex that wrongly removed the semanticspeakerclass).Tests: +17 new across unit and e2e (escaping, storage order-shift/corruption/markup-name, serialization format, round-trip, style scrub, caret/selection/IME/re-index). Full suite 36 e2e + 15 unit passing.
Closes #406, #408, #409, #410, #415.