Skip to content

slides: a save drops the assets nothing refers to, so a deck can shrink - #447

Merged
nyblnet merged 1 commit into
mainfrom
slides-prune-assets
Sep 12, 2026
Merged

slides: a save drops the assets nothing refers to, so a deck can shrink#447
nyblnet merged 1 commit into
mainfrom
slides-prune-assets

Conversation

@nyblnet

@nyblnet nyblnet commented Sep 11, 2026

Copy link
Copy Markdown
Owner

Fixes #442.

What was wrong. doc.assets was append-only. Add images, delete every slide, save — the "empty" deck was still 20 MB, because deleting an element removes the reference and nothing ever removed the bytes. A deck could only ever grow. Confirmed on main: no code path prunes doc.assets.

The fix. Slides' own save facade (slides/src/save.ts) now shadows serializeAuto and serializeFile with wrappers that hand the kernel a copy of the document with unreferenced assets removed. The kernel never learns the slides document shape. Every path that writes a .bento.html from this app goes through the facade — ⌘S write-back, the download fallback, the four export copies, and window.bento.serialize().

The live document is never touched. Undo history in memory still holds the deleted element and its asset, so ⌘Z after a save brings both back and the next save keeps the asset because it is referenced again.

Measured, same scenario on both builds — one image plus two orphaned 1 MB assets, then delete everything and save:

file with image after deleting everything
main 3.74 MB, 3 assets 3.74 MB, 3 assets
this PR 1.75 MB, 1 asset 0.75 MB, 0 assets

Real headless Chrome through the built shell and window.bento.serialize(), not the pure function in isolation.

The one design choice worth reading. referencedAssetKeys enumerates every reference form the format has rather than walking generically, because the format uses two conventions — asset:<key> on image.src, media.src, media.poster; bare keys on svg.asset, code.grammarAssetId, code.themeAssetId, fonts[].asset — and a walk that knew only one would silently delete the other. Dropping too much is the worse failure (an image gone from a file with nothing on screen to say so), so the new rig scripts/test-slides-assets.ts pins each form individually and is weighted toward "kept". A count would pass with the wrong six surviving. A future field that references an asset must be added to that function, and the rig is where a miss shows up.

blobs (the live-collab offload map) is pruned to the same key set.

test-export-secrets.ts — its serializeFile call-site guard is made precise, not looser: it now matches a call and not a function serializeFile( definition, which is what the facade wrapper is. Verified by injecting a fresh plain serializeFile( call into autosave.ts: the guard still fails on it. Flagging this explicitly for security, since it's a security-flavoured rig: the property it protects — no user-facing path writes plaintext — is unchanged, and the wrapper's only caller is still window.bento.serialize().

Collab. The CRDT syncs assets per key, so pruning the saved copy cannot lose a peer their asset. One narrow gap, documented at the function: a file that pruned key K, was reopened, and then receives a peer's op re-referencing K (an undo on the peer, say) shows that image broken on that replica only, until re-added. Rare and non-corrupting; the trade against every deck growing without bound is not close.

Prior art, credited. @1eevy's #372 addresses this same bug as one part of a larger save-time compactor (dedupe, key renaming, data-URI interning) bundled with three UI features and a kernel hook, deferred to a later release. This is the narrow fix and does not preclude that compaction landing later.

Not in this PR: the CHANGELOG entry — that's scribe's file.

Verified: tsc -b, build:single, shell gate, test-ci-registered 290/290 (the new rig is registered), preview 28/28, savepurpose 16/16, export-secrets 57/57, new rig 25/25.

doc.assets was append-only. Add images, delete every slide, save — and the
"empty" deck was still 20 MB (#442), because deleting an element removes the
reference and nothing ever removed the bytes. A deck could only ever grow.

The fix lives in slides' own save facade, which now shadows serializeAuto and
serializeFile with wrappers that hand the kernel a COPY of the document with
unreferenced assets removed. The kernel never learns the slides document
shape; the live document is never touched, so undo after a save still brings
an image back and the next save keeps it. Every path that writes a .bento.html
from this app goes through the facade: ⌘S write-back, the download fallback,
the four export copies, and window.bento.serialize().

referencedAssetKeys enumerates every form the format has rather than walking
generically, because the format uses two conventions — `asset:<key>` on
image/media src and media poster, and BARE keys on svg.asset, code's
grammar/theme ids and fonts[].asset — and a walk that knew only one would
delete the other. That enumeration is the cost of this change and the rig pins
each form individually; a count would pass with the wrong six surviving.

blobs (the live-collab offload map) follows assets to the same key set.

Measured, matched pair on the same scenario (one image plus two orphaned
1 MB assets, then delete everything and save):
  main:    3.74 MB / 3 assets  ->  3.74 MB / 3 assets
  patched: 1.75 MB / 1 asset   ->  0.75 MB / 0 assets

test-export-secrets' serializeFile call-site guard is made precise rather than
looser: it now matches a CALL and not a `function serializeFile(` DEFINITION,
which is what the facade wrapper is. Verified by injecting a fresh plain call
into autosave.ts — the guard still fails on it.

Under live collaboration the CRDT syncs assets per key, so pruning the saved
copy cannot lose a peer their asset. The one gap is documented at the
function: a file that pruned K, reopened, then received a peer's op
re-referencing K shows that image broken on that replica only, until re-added.

Prior art: 1eevy's #372 addresses the same bug as one part of a larger
save-time compactor (dedupe, key renaming, data-URI interning) bundled with
three UI features and a kernel hook, and is deferred to a later release. This
is the narrow fix and does not preclude that landing later.
@nyblnet nyblnet mentioned this pull request Sep 11, 2026
@github-actions

Copy link
Copy Markdown

Build size

main (0100083) → slides-prune-assets (823c710)

app base PR change
bento/slides 677.9 KiB 678.4 KiB +0.5 KiB (+0.07%)
bento/spaces 271.3 KiB 271.3 KiB 0.0 KiB (0.00%)
bento/dash 424.1 KiB 424.1 KiB 0.0 KiB (0.00%)

Updated: 2026-09-11T22:12:25Z

@nyblnet
nyblnet merged commit 401cade into main Sep 12, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

File size never decrease

1 participant