Skip to content

fix(upload): eliminate CSV upload freeze, per-cell JSON.parse storm, and large-output hang - #220

Merged
obvious-autobuild-staging[bot] merged 1 commit into
masterfrom
fix/csv-upload-freeze
Oct 15, 2026
Merged

obvious-autobuild-staging[bot] merged 1 commit into
masterfrom
fix/csv-upload-freeze

Conversation

@obvious-autobuild-staging

Copy link
Copy Markdown
Contributor

Human author: David Boskovic ([email protected])

Why

Uploading a multi-megabyte CSV file (e.g. 60k–250k rows) froze the browser tab across two consecutive synchronous waves (7.24 s at 60k rows; 15.5 s at 120k rows; >39 s at 250k rows), flashed a false "Resolve the error to see output." message while conversion was in flight (error === null), and risked crashing the renderer into a blank page when mounting CodeMirror's Lezer syntax tree over 15–65 MB pretty-printed JSON or when transpose: true overflowed the V8 call stack above 125k rows.

What

  1. 70× faster applySmartValues (app/src/lib/convert.ts): Guard tryParseJsonLiteral by trimmed first/last characters (true, false, null, {...}, [...], "...") before invoking JSON.parse, eliminating ~600,000 thrown SyntaxError exceptions on a 60k-row × 10-col CSV (2,188 ms → ~23 ms).
  2. Stack-safe iterative transpose (app/scripts/patch-csv2json.mjs): Replace zip.apply(this, a) in csvjson-csv2json with an iterative row-by-column loop so transpose: true on >125,000 rows never throws RangeError: Maximum call stack size exceeded.
  3. Single-pass conversion on file upload & bounded header scan (app/src/App.tsx, app/src/components/InputPane.tsx): Bypass the typing debounce (150 ms / 1,000 ms) when readFile or handleUploadEncodingChange sets input, and fire tracker.discrete from an effect after the conversion result is computed so convertText runs once per upload instead of twice. In InputPane, slice only the first CSV record before detectIndexedHeaderGroups so row-0 header inspection never parses all 60k+ rows.
  4. Honest pending conversion state (app/src/components/OutputPane.tsx): Render Converting… (data-testid="output-converting-placeholder") when !inputEmpty && !outputText && !error, reserving "Resolve the error to see output." strictly for Boolean(error).
  5. Large-document CodeMirror protection (app/src/components/JsonCodeMirror.tsx): Skip heavy Lezer syntax/fold/bracket extensions above LARGE_DOC_MAX_CHARS = 512 * 1024, and cap read-only JSON editor previews above READONLY_PREVIEW_MAX_CHARS = 2_000_000 to the first 512 KB (aligned to a line boundary) with data-testid="large-output-preview-banner" while Copy and Download continue to export the full output.
  6. Pane ErrorBoundary & UTF-16LE/BE BOM auto-decode (app/src/components/ErrorBoundary.tsx, app/src/lib/files.ts, app/src/changelog/entries.ts): Wrap both converter panes in a recoverable ErrorBoundary, sniff 0xFF 0xFE / 0xFE 0xFF BOMs in decodeUpload when encoding === "utf-8", add changelog entry #13, and rebuild app/dist.

Tradeoffs

  • 512 KB editor preview cap above 2 MB read-only output vs. full DOM mount: Constructing a CodeMirror document over 15–65 MB of pretty-printed JSON takes 4.5–28s and can OOM the renderer tab. Capping the read-only editor buffer to the first 512 KB above 2 MB keeps the UI instant while preserving 100% of the converted JSON on Copy and Download.

How to Review

  • app/src/lib/convert.ts & app/scripts/patch-csv2json.mjs — tryParseJsonLiteral fast-path guard and iterative transposeRows patch
  • app/src/App.tsx & app/src/components/InputPane.tsx — immediate debouncedInput sync on upload, post-conversion tracker.discrete, and sliceFirstCsvRecord
  • app/src/components/OutputPane.tsx & app/src/components/JsonCodeMirror.tsx — output-converting-placeholder and LARGE_DOC_MAX_CHARS / READONLY_PREVIEW_MAX_CHARS guards
  • app/src/components/ErrorBoundary.tsx & app/src/lib/files.ts — recoverable pane error boundary and UTF-16 BOM sniffing
  • app/src/lib/__tests__/convert.test.ts, app/src/lib/__tests__/files.test.ts, app/src/App.test.tsx — unit and component regression tests

Verification

  • cd app && npm test (30 test suites, 511 tests passing)
  • cd app && npm run lint && npx tsc -b --noEmit && npm run build
  • bash .github/scripts/verify-seo.sh
  • Headless Chromium (agent-browser) benchmark on http://127.0.0.1:4173: uploading a 60,000-row × 10-column CSV (6.29 MB → 15.75 MB JSON) completed in a single pass (979 ms total long-task time vs. 7,240 ms pre-fix) with sawFalseError: false and rendered large-output-preview-banner.

🔗 Obvious Project · 🧵 Obvious Thread · 🔀 Obvious PR Artifact

…and large-output hang

- Guard tryParseJsonLiteral by first/last character before calling JSON.parse so non-JSON cells never throw SyntaxError exceptions (70x faster applySmartValues on 60k-row CSVs)
- Replace zip.apply(this, a) in csv2json transpose patch with an iterative loop so >125k rows never overflow the V8 call stack
- Bypass typing debounce on discrete file uploads/encoding changes and fire tracker.discrete from computed conversion state so convertText runs once per upload
- Slice only the first CSV record in InputPane before detectIndexedHeaderGroups so header detection never parses all rows
- Render "Converting…" instead of "Resolve the error to see output." when conversion is pending with error === null
- Skip heavy CodeMirror Lezer syntax/fold/bracket extensions above 512 KB and cap read-only JSON editor preview above 2 MB with a notice banner while preserving full output in Copy/Download
- Add ErrorBoundary around converter panes and auto-detect UTF-16LE/BE BOMs in decodeUpload
- Add bundled changelog entry #13 and rebuild app/dist

Co-authored-by: David Boskovic <[email protected]>
@obvious-autobuild-staging
obvious-autobuild-staging Bot marked this pull request as ready for review October 15, 2026 18:49
@obvious-autobuild-staging
obvious-autobuild-staging Bot merged commit 1c1b19a into master Oct 15, 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.

2 participants