Skip to content

fix: crashes, vanishing PDFs, PDF export OOM; capture crash details (1.3.3) - #33

Merged
markm39 merged 2 commits into
mainfrom
fix/crash-hardening
Oct 2, 2026
Merged

markm39 merged 2 commits into
mainfrom
fix/crash-hardening

Conversation

@markm39

@markm39 markm39 commented Oct 2, 2026

Copy link
Copy Markdown
Contributor

Why

App Store reviews on 1.3.2 report crashes "during work" and a PDF that "disappeared... annotations are still there". Xcode Organizer has 27 crash logs (1.0–1.3.2, all iPad, all SIGABRT), symbolicated with the official React Native and Hermes 0.81.5 dSYMs. They fall into four groups, each addressed here.

Crash / bug Logs Root cause (proven) Fix
CALayerInvalidGeometry: position contains NaN in updateSelectionToolbarFrame 5 (1.2) mobile-ink clear() kept a stale selection, so bounds were ±FLT_MAX. CGRect.width passed the guard and midX became NaN. Reproduced with the shipped engine and a real CALayer. mobile-ink 0.3.5 (engine) + 0.3.6 (finite guard)
Delete after Undo aborts (std::length_error) n/a (reproduced) Stale selection indices after undo cause a size_t underflow in reserve. The same bug deleted unselected strokes. mobile-ink 0.3.5
Hermes out of memory 5 (1.3.1/1.3.2) PDF export held every page raster as base64 in JS, regex-escaped each, and concatenated them into one HTML string. Symbolicated stacks match escapeHtml, and concat in buildHtml. Print in 8-page passes and merge natively (Core Graphics on iOS, PdfRenderer on Android). Data URIs are no longer escaped.
Uncaught JS exception at launch (performVoidMethodInvocation → RCTFatal) 16 (1.0–1.3.2) Apple's logs drop the JS message. Ruled out: locale/Intl, Open-In URL routing. Capture: a global handler writes the error synchronously before termination, a root ErrorBoundary gives a recovery screen, and the next launch offers Share (never sent automatically).
PDF vanishes, annotations remain (review) n/a (reproduced) Stored pdfUri / image URIs were absolute and embedded the app container UUID, which iOS changes on updates, reinstalls and iCloud restores. Observed the container change 4 times in one simulator session. Catalog derives pdfUri from the note id and persists the healed value once. Note bodies re-root inserted-image paths.
"Unmatched Route" page for unknown links / Open-In n/a expo-router had no fallback. +native-intent maps file:/content: to /, and +not-found redirects.

Verification

  • Release simulator builds on iPad Pro 13" (Hermes bytecode, like production):
    • Stale-path PDF note is blank on the old code and renders on the new code.
    • 21-page PDF export yields one 21-page Letter PDF. Batch files are removed and the first and last pages are rendered and checked.
    • Fatal JS error: report written before termination, banner on next launch, Share opens the share sheet.
    • Render error: recovery screen, report recorded, "Try again" recovers.
    • Unknown deep link lands on the library. The catalog heals on disk on first launch.
  • Tests: 79 unit tests (new: catalog heal, image path rebasing, crash report, export passes, merge failure cleanup, base64 embedding). npm run typecheck passes.
  • Android: PDFUtilsModule.kt compiles with Kotlin 2.1.20 against SDK 36 and React Native 0.81.5. Not run on a device or emulator.
  • Native merge: verified separately on real PDFs (page count and media box preserved).

Known trade-off

On Android, exports longer than 8 pages are merged by rasterizing pages at 2x, because Android has no vector PDF merge API. Text boxes in those exports aren't selectable, and the merge isn't device-tested. iOS keeps vector pages. Android ships from source only.

Release

1.3.3 (16) with mobile-ink 0.3.6 and localized release notes in all 13 store locales.

Vanishing PDFs (App Store review, 1.3.2): notes stored absolute file://
URIs that embed the app container path, which iOS changes on updates,
reinstalls and iCloud restores. The live canvas then loaded a missing PDF:
annotations stayed, the PDF disappeared. The catalog now derives each
note's pdfUri from its id (and persists the healed value once), and note
bodies re-root inserted-image paths under the current documents folder.

PDF export out of memory (Hermes OOM crash group, 1.3.1/1.3.2): every
page raster was held as base64 in JS, regex-escaped and concatenated into
one HTML string. Export now renders and prints 8 pages per pass and merges
the pass PDFs natively (Core Graphics on iOS, PdfRenderer on Android).
Base64 data URIs are no longer escaped (they cannot contain HTML-special
characters).

Crash capture (JS fatal crash group, 1.0-1.3.2; App Store logs drop the
JS message): a global JS error hook records the error synchronously
before the app terminates, a root ErrorBoundary turns render errors into
a recovery screen, and the next launch offers to share the report. Nothing
is sent automatically. Strings are localized in all 12 locales.

Unknown deep links and Open-In file URLs now open the library instead of
expo-router's Unmatched Route page.
mobile-ink 0.3.5/0.3.6 fix the selection crashes (Delete after Undo,
CALayer NaN toolbar frame on pooled page reload). Localized release
notes for all 13 store locales.
@markm39
markm39 merged commit 5eb7079 into main Oct 2, 2026
1 check passed
@markm39
markm39 deleted the fix/crash-hardening branch October 2, 2026 21:41
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