fix(pptx): keep source bytes resolvable after state cloning#47
Merged
karthikmudunuri merged 1 commit intoMay 14, 2026
Merged
Conversation
1.12.0's chrome / EMF / slide-bg preservation relied on a non-enumerable
__slidewiseSourcePptx attachment that structuredClone and { ...deck }
reducer spreads silently strip. As soon as the user edited anything the
attachment was gone, serializeDeck had no source to inject from, and
saves fell back to pptxgenjs's lossy emitter — the exact regression
1.12.0 was supposed to fix.
parsePptx now stamps an enumerable Deck.sourcePptxId and stashes the
source bytes in a module-level cache keyed by that id. resolveSource
looks them up by id when no explicit options.source is passed. The id
survives structuredClone, spread, and JSON round-trip, so any
reducer-driven host (Zustand, Redux, useState, Immer) keeps the
preservation pipeline alive across edits.
New regression test mirrors the broken scenario (parsePptx →
structuredClone + spread → serializeDeck with no source) and asserts
all 28 eon-deck layouts + 5 embedded fonts still make it into the
saved file.
5 tasks
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.
Summary
1.12.0 shipped the master / layout / theme / font / EMF / slide-bg preservation pipeline — but it was silently no-op'ing in practice. The pipeline depended on a non-enumerable
__slidewiseSourcePptxattachment on the deck.structuredClone(used bysnap()for history) AND every{ ...deck, ... }reducer spread strip non-enumerable properties. So:parsePptxattaches source bytes (non-enumerable){ ...deck, slides }→ source attachment goneresolveSourcereturnsundefined→preserveUnknownsbails immediately → pptxgenjs writes a stripped-down deckEnd user sees: 28 layouts → 1, 5 embedded fonts → 0, half the slides empty. Exactly what they reported on 1.12.0.
Fix
Move the source attachment to an enumerable identifier on the deck, with bytes held in a module-level cache:
parsePptxstampsdeck.sourcePptxId = nanoid()(enumerable, JSON-safe string).Map<string, ArrayBuffer>caches source bytes keyed by that id.resolveSourceconsults the cache when no explicitoptions.sourceis passed, falling back to the legacy non-enumerable attachment for direct callers.structuredClone, spread,JSON.parse(JSON.stringify(deck))all preservesourcePptxIdbecause it's a plain enumerable string, so the cache lookup keeps working across every reducer mutation.The cache is in-memory only — cross-session round-trips (page reload → rehydrate from localStorage) still need the host to re-attach source bytes via
serializeDeck(deck, { source }). Documented on the field's TSDoc.Validation
New regression test in
chrome-preservation.test.ts: parseseon-deck.pptx, runs the deck throughstructuredClone+ an object spread (mirroring the store'ssnap()and a reducer), saves with NO explicit source, asserts 28/28 layouts and 5/5 embedded fonts survive.End-to-end against the user's exact scenario (
/tmp/edit_sim_no_source_passed.mjs):Versus the user's broken save on 1.12.0 with the same input:
1 layout / 0 fonts / many empty slides.Test plan
pnpm typecheckcleanpnpm --filter @textcortex/slidewise test— 38/38 passingeon-deck-v1.pptxwith nosourceoption produces fully-preserved outputChangeset
Patch bump on
@textcortex/slidewise(1.12.0 → 1.12.1) — bug fix on a regression that landed in 1.12.0; no API breaks.Deck.sourcePptxIdis a new optional field, additive.