Skip to content

Give the odrcore wrapper typed errors and stop crashing on purpose - #114

Merged
andiwand merged 2 commits into
mainfrom
modernize/07-core-wrapper
Jul 26, 2026
Merged

Give the odrcore wrapper typed errors and stop crashing on purpose#114
andiwand merged 2 commits into
mainfrom
modernize/07-core-wrapper

Conversation

@andiwand

Copy link
Copy Markdown
Member

Stacked on #113.

CoreWrapper reported failures through an NSNumber errorCode carrying -2, -3 and -5, decoded again in Document.swift and a third time in DocumentViewController. It also assigned 0 to an object pointer to mean success, so XCTAssertNil(coreWrapper.errorCode) was passing for the wrong reason. It now uses an NSErrorDomain + NS_ERROR_ENUM, which Swift imports as throwing methods.

Crashes fixed

  • pagePaths[page] unchecked. The page filter drops every page for some document shapes (a spreadsheet whose only page is named document), so an empty array was reachable and indexing it killed the app. translate now fails with an error rather than reporting success with nothing to show, and the index is clamped.
  • backTranslate dereferenced an empty std::optional when called before any translate. Now a typed error — covered by a new test.
  • writeContents leaked its DispatchGroup. enter() happened inside the JS completion handler and leave() only on the happy path, so a failing generateDiff left it unbalanced and every save burned the full 30s timeout before throwing. It also called fatalError from that handler. Replaced with a semaphore signalled on every path, plus a main-thread guard so hopping to main can't deadlock.
  • Eight fatalError calls used as error handling, for states users actually reach: no document, failed import, missing storyboard identifier, root controller of an unexpected type. Each now recovers or reports.
  • Three as! casts on storyboard lookups, and the password alert's textFields![0] / .text!.
  • documentEncrypted rescheduled itself when the view wasn't in a window yet — and then presented the alert anyway.

Other cleanups

  • DocumentDelegate: classAnyObject
  • @UIApplicationMain@main
  • UIApplication.shared.openURLopen(_:) (deprecated since iOS 10)
  • UserDefaults.synchronize() (no-op since iOS 12)
  • The progress bar observed UIDocument.progress, not the loadProgress that parse() actually advances — so it never moved.
  • The onboarding buttons were hardcoded English in Constants while the app ships 17 localizations. Now NSLocalizedString with intro_next/intro_skip/intro_start added to en.lproj; other languages fall back to English exactly as they do today, but are now translatable via Crowdin. The rest of Constants was dead code.

Verification

  • Both flavors build; both install and launch in a simulator and stay running.
  • Test suite goes from 1 test (a measure block asserting a nil error code) to 5, all passing: page output, back translation, backTranslate before translate, unsupported file type, and the performance measurement.

Deliberately not in this PR

The actor + async/await restructure of CoreWrapper. parse() still runs the C++ translate synchronously on whatever thread the didSet fired on. That change alters threading semantics, and with no integration coverage over document loading I would be changing behavior I cannot verify. It wants its own PR with tests written first — say the word and I will do it next.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a50c161555

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread OpenDocumentReader/ContentViewController.swift
@andiwand
andiwand force-pushed the modernize/07-core-wrapper branch from a50c161 to 8288c9d Compare July 26, 2026 08:15
@andiwand

Copy link
Copy Markdown
Member Author

Correct, fixed in 30864e6.

A key missing from a table resolves to the key itself rather than falling back to the development language, so the other 16 localizations would have rendered intro_next / intro_start on the button where they previously showed the hardcoded English.

Went with the explicit-value option rather than seeding 16 tables with untranslated English:

NSLocalizedString("intro_start", value: "Start", comment: "onboarding button on the last page")

Verified the fallback actually resolves to Start / Next and not the key when no matching table exists. Translations can land per-locale later without touching the call site.

One loose end from the same commit, out of scope here: intro_skip was added to en.lproj but nothing reads it — the Skip button's title is still hardcoded in Main.storyboard and has no outlet.

andiwand and others added 2 commits July 26, 2026 10:47
CoreWrapper reported failures through an NSNumber errorCode carrying -2,
-3 and -5, decoded again in Document.swift and a third time in
DocumentViewController. It also assigned 0 to an object pointer to mean
success. It now uses a proper NSError domain with an NS_ERROR_ENUM, so
Swift sees throwing methods and the magic numbers are gone.

Crashes fixed along the way:

- parse() indexed pagePaths[page] unchecked. The page filter drops every
  page for some documents, so an empty array was reachable and indexing it
  killed the app. translate now fails with an error instead of reporting
  success with nothing to show, and the index is clamped.
- backTranslate dereferenced an empty std::optional when it ran before any
  translate. Now a typed error.
- writeContents entered a DispatchGroup inside the completion handler and
  only left it on the happy path. A failing generateDiff left it unbalanced
  and every save waited out the full 30s timeout. It also called
  fatalError from that handler. Replaced with a semaphore signalled on
  every path.
- eight fatalError calls used as error handling, for states a user can
  actually reach: no document, failed import, missing storyboard entry,
  root controller of an unexpected type. All either recover or report.
- three as! casts on storyboard lookups.
- the password alert force unwrapped textFields![0] and .text!.
- documentEncrypted rescheduled itself when the view was not in a window
  yet, then presented the alert anyway.

Other cleanups:
- DocumentDelegate: class -> AnyObject
- @UIApplicationMain -> @main
- UIApplication.shared.openURL -> open(_:), deprecated since iOS 10
- UserDefaults.synchronize(), a no-op since iOS 12
- the progress bar observed UIDocument's own progress, not the loadProgress
  that parse() actually advances
- the onboarding buttons were hardcoded English in Constants while the app
  ships 17 localizations. They are NSLocalizedString now, with the keys
  added to en.lproj for translation. The rest of Constants was dead.

Tests go from one measure block asserting a nil error code to five cases
covering page output, back translation, both new error paths and the
performance measurement.
Moving the button titles out of Constants into NSLocalizedString added the
keys to en.lproj only. A key missing from a table does not fall back to the
development language, it resolves to the key itself, so all 16 other
localizations would have shown "intro_next" / "intro_start" on the button
where they used to show the hardcoded English.

Passing value: keeps that English wording until the strings are translated.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011MhKU2kWm1cPW4GBq9gon5
@andiwand
andiwand force-pushed the modernize/07-core-wrapper branch from 30864e6 to d44e7c3 Compare July 26, 2026 08:49
@andiwand
andiwand changed the base branch from modernize/06-spm to main July 26, 2026 08:49
@andiwand
andiwand merged commit c7d85b2 into main Jul 26, 2026
3 checks passed
@andiwand
andiwand deleted the modernize/07-core-wrapper branch July 26, 2026 08:52
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