Skip to content

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

Merged
DavidBabinec merged 1 commit into
mainfrom
fix/export-size-estimate-accuracy
Jun 16, 2026
Merged

fix(export): server-accurate export size estimate (incl. media)#64
DavidBabinec merged 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>
@DavidBabinec DavidBabinec marked this pull request as ready for review June 16, 2026 16:34
@DavidBabinec DavidBabinec merged commit 2eb0999 into main Jun 16, 2026
6 checks passed
@DavidBabinec DavidBabinec deleted the fix/export-size-estimate-accuracy branch June 30, 2026 23:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

1 participant