Skip to content

fix(export): server-accurate export size estimate (incl. media)#64

Draft
DavidBabinec wants to merge 1 commit into
mainfrom
fix/export-size-estimate-accuracy
Draft

fix(export): server-accurate export size estimate (incl. media)#64
DavidBabinec wants to merge 1 commit into
mainfrom
fix/export-size-estimate-accuracy

Conversation

@DavidBabinec

Copy link
Copy Markdown
Contributor

What

The Estimated size line in the Export dialog was a client-side guess that:

  1. Ignored media entirelymediaAssetCount was hardcoded to 0 (ExportDialog.tsx:205, comment: "caller doesn't provide a cheap count yet"), so toggling Media files never changed the number.
  2. Underestimated content ~10× — flat constants (8 KB shell + 1.5 KB/row) that don't model real page-trees/styleRules.

This read as "media isn't being exported" — but the export is correct; only the estimate was wrong. (Verified separately: a real export of a 19-image site embeds all binaries as base64 and round-trips on import. See the analysis in the conversation that produced this PR.)

Fix — one source of truth

Replace the heuristic with an exact, server-computed estimate that reuses the export's own selection logic so the number can never drift from the real download:

  • New POST /admin/api/cms/export/estimate — runs the identical table/row/media selection as the real export, but computes the byte size instead of streaming the bundle. It never reads media files off disk or base64-encodes them: it serializes the real selection with empty bytesBase64 strings and adds each asset's analytic Base64 length (4·⌈n/3⌉). Base64 is ASCII, so this equals the real bundle byte length exactly.
  • export.ts refactorbuildBundle + the media-metadata mapping are shared between the download and estimate paths, so they can't diverge. Behavior of the existing download path is unchanged.
  • useExportEstimate — now fetches the endpoint (debounced 250 ms, cancellable via AbortController) and formats the result; the dialog mirrors the exact ExportRequest it will send. The previous value stays on screen while a new estimate is in flight (no flicker); failures show unavailable.

Verification

  • bun run build ✓ · bun run lint ✓ · bun test ✓ — 5439 pass, 0 fail
  • New tests assert estimate === real download byte length for the no-media, no-shell, and embedded-media cases (cmsTransferExport.test.ts), plus a dialog test that the estimate re-fetches and grows when Media is toggled (exportDialog.test.tsx).
  • Live, against a real site (19 images, ~69 MB), on the running server:
    • estimate without media = 183,618 B (~179 KB)
    • estimate with media = 96,769,329 B → actual download with media = 96,769,329 B (byte-identical)
    • Dialog screenshot confirmed: Media off → ~179 KB, Media on → ~92.3 MB.

Note: the pre-push hook surfaces 6 react-doctor warnings on ExportDialog.tsx (lines 142/144/165/264/279 — the useState cluster, reset-on-open pattern, and <label>/Switch markup). All are pre-existing patterns unrelated to this change; they only appear because the file is in the diff. bun run lint (the CI gate) passes.

🤖 Generated with Claude Code

… media

The "Estimated size" line in the Export dialog was a client-side guess that
ignored media entirely (`mediaAssetCount` was hardcoded to 0) and underestimated
content ~10x (flat 8 KB shell + 1.5 KB/row constants). Toggling "Media files"
never moved the number, which read as "media isn't exported" — but media *is*
exported correctly; only the estimate was wrong.

Replace the heuristic with an exact, server-computed estimate that reuses the
export's own selection logic as a single source of truth:

- New `POST /admin/api/cms/export/estimate` runs the identical table/row/media
  selection as the real export but computes the byte size instead of streaming
  the bundle. It never reads media files off disk or base64-encodes them:
  it serializes the real selection with empty `bytesBase64` strings and adds
  each asset's analytic Base64 length (4*ceil(n/3)). Because Base64 is ASCII,
  this equals the real bundle byte length exactly.
- `export.ts` is refactored to share `buildBundle` + media-metadata mapping
  between the download and estimate paths, so they cannot drift.
- `useExportEstimate` now fetches this endpoint (debounced, cancellable) and
  formats the result; the dialog mirrors the exact ExportRequest it will send.

Verified live against a real site (19 images, ~69 MB): estimate without media =
183,618 B, with media = 96,769,329 B — byte-identical to the actual download.
Tests assert estimate === real download size for the no-media, no-shell, and
embedded-media cases.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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.

1 participant