Part of the #403/#404 Phase A push. The .hyperaudio reader/writer in #404 diverges from the format's reference behavior in ways that break cross-implementation round-trips. All fixes are contained and testable against the shared fixture suite (spec repo).
- Media path validation: reject
/, \, and the exact segments ./.. — but permit .. as a substring (mix..final.mp3 is legal). The current any-substring rejection makes conforming containers written elsewhere unreadable; this exact bug class has already occurred in the wild once.
- Sanitize media filenames with one
sanitizeMediaFilename() shared by descriptor construction, OPFS storage, and ZIP entry creation — writer and reader must share the rule.
- Envelope preservation: keep the parsed raw envelope in the session; on save,
structuredClone it and overwrite only editor-owned fields (merge owned keys inside known nested objects). Unknown top-level and nested fields must survive an open→save round trip.
- Text caps in UTF-8 bytes: read entries as
uint8array, enforce the 50 MB cap on bytes, decode with new TextDecoder('utf-8', {fatal: true}) so invalid UTF-8 is rejected consistently.
- Reject non-STORED media entries (or enforce a strict streaming size cap) — matching the format's requirement and avoiding JSZip private-field reliance.
Acceptance: the shared fixtures (legal .. names, unicode/hostile names, unknown fields, newer minors/majors, invalid UTF-8, compressed media, oversized text, link media, missing media, captions variants, original-transcript preservation) pass in both directions.
Part of the #403/#404 Phase A push. The .hyperaudio reader/writer in #404 diverges from the format's reference behavior in ways that break cross-implementation round-trips. All fixes are contained and testable against the shared fixture suite (spec repo).
/,\, and the exact segments./..— but permit..as a substring (mix..final.mp3is legal). The current any-substring rejection makes conforming containers written elsewhere unreadable; this exact bug class has already occurred in the wild once.sanitizeMediaFilename()shared by descriptor construction, OPFS storage, and ZIP entry creation — writer and reader must share the rule.structuredCloneit and overwrite only editor-owned fields (merge owned keys inside known nested objects). Unknown top-level and nested fields must survive an open→save round trip.uint8array, enforce the 50 MB cap on bytes, decode withnew TextDecoder('utf-8', {fatal: true})so invalid UTF-8 is rejected consistently.Acceptance: the shared fixtures (legal
..names, unicode/hostile names, unknown fields, newer minors/majors, invalid UTF-8, compressed media, oversized text, link media, missing media, captions variants, original-transcript preservation) pass in both directions.