Skip to content

release: cut v2.0.6 "Harbor" ("Parity" — iOS feature parity + opt-in crash reporting) - #245

Merged
doublegate merged 3 commits into
mainfrom
feat/v2.0.6-ios-parity
Jul 9, 2026
Merged

doublegate merged 3 commits into
mainfrom
feat/v2.0.6-ios-parity

Conversation

@doublegate

Copy link
Copy Markdown
Owner

Summary

The second release of the iOS finalization window (v2.0.5 → v2.0.8), on the byte-identical v2.0.0 "Timebase" core (AccuracyCoin 141/141, nestest 0-diff, #![no_std] chip stack untouched). Host / iOS-only.

What landed

  • New opt-in crash reporting (CrashReporter.swift) — iOS analogue of the Android v1.8.8 CrashReporter, closing the v1.9.9 readiness gap. Off by default (Settings → Diagnostics); when enabled, an uncaught-NSException handler writes local crash logs (viewable + copyable in-app via a CrashLogsView; nothing uploaded → "Data Not Collected" label unchanged). Handler re-checks the live opt-in at crash time. EN + ES. Scope is honest: ObjC/NSException only; Swift signal-traps left to a maintainer's third-party reporter (same posture as Android's Crashlytics note).
  • Feature-parity re-verification of the v1.9.x host features (Game Center, CloudKit save sync, MFi controllers, capture/PiP, accessibility) against the unchanged v2.0.0 bridge surface — docs/ios-v2.0.6-readiness.md.
  • Version: workspace 2.0.5 → 2.0.6; iOS MARKETING_VERSION → 2.0.6.

Quality

An independent Swift review pass caught + fixed a real bug pre-commit: the exception-handler closure originally captured the previous handler as a local, which can't convert to a @convention(c) function pointer — now a static. The review also flagged a pre-existing deploymentTarget.iOS 15.0 vs actual (NavigationStack/.topBarTrailing, iOS 16/17) discrepancy; the new code follows the file's established pattern and doesn't change the target — reconciling it is flagged for v2.0.7.

Verification (host gates — all green)

cargo check --workspace (version-only lock cascade) · fmt · clippy --workspace --all-targets -D warnings (no issues) · RUSTDOCFLAGS=-D warnings cargo doc · no_std cross-compile · both wasm32 clippy gates · markdownlint · Localizable.xcstrings JSON validity. Core untouched → determinism / AccuracyCoin 141/141 unaffected. Swift compile + on-device crash-capture remain the documented macOS/TestFlight carryover (flagged for v2.0.9).

Docs

CHANGELOG [2.0.6] · docs/STATUS.md (chain demoted) · to-dos/ROADMAP.md · .github/release-notes/v2.0.6.md · docs/ios.md (Parity section) · new docs/ios-v2.0.6-readiness.md.

TestFlight-only; App Store + AltStore PAL deferred to v2.1.0.

🤖 Generated with Claude Code

…crash reporting)

The second iOS finalization release (the v2.0.5→v2.0.8 window) on the
byte-identical v2.0.0 "Timebase" core: AccuracyCoin 141/141, nestest 0-diff, the
#![no_std] chip stack untouched. Host / iOS-only — no accuracy / save-state /
determinism number moves.

New: opt-in crash reporting (CrashReporter.swift) — the iOS analogue of the
Android v1.8.8 CrashReporter, closing the v1.9.9 readiness gap.

- Privacy-first, OFF by default. AppModel.crashReportingEnabled (a @published,
  UserDefaults-backed, non-cloud-synced flag) gates everything; with it off the app
  installs no handler and writes nothing, so the "Data Not Collected" label and
  PrivacyInfo.xcprivacy are unchanged. Exposed as a Settings > Diagnostics toggle.
- Local-only. On opt-in, CrashReporter.install(enabled:) chains an
  NSSetUncaughtExceptionHandler that writes a timestamped report (version, device,
  OS, exception name/reason, callStackSymbols) to
  Application Support/RustyNES/crash-logs/, pruned to the newest 10. Nothing is
  uploaded; a CrashLogsView (list -> detail -> Copy / Clear) lets the user read and
  share manually. New UI strings localized EN + ES.
- Correct C-callback discipline: the handler closure captures NOTHING (only static
  refs — previousHandler, writeLog, the live opt-in flag), so it is convertible to
  the @convention(c) NSUncaughtExceptionHandler; the previous handler is chained via
  a static var, not a captured local. It re-checks the live opt-in at crash time so
  opting out stops new logs immediately, and installs once (idempotent) from
  AppModel.init at launch + lazily on the Settings toggle.
- Honest scope: NSSetUncaughtExceptionHandler catches the ObjC/NSException class
  only; pure-Swift signal-based traps are left to a maintainer's third-party
  reporter (gated on the same flag), the same posture Android takes toward
  Crashlytics. Documented inline.

Feature-parity re-verification: the v1.9.x host features (Game Center, CloudKit
save sync, MFi controllers, ReplayKit capture/PiP, accessibility) were re-checked
against the unchanged v2.0.0 bridge surface (the timebase rewrite touched only the
scheduler internals) and all still route through the same typed bridge + the
re-ported save-state epoch. Audit recorded in docs/ios-v2.0.6-readiness.md.

An independent Swift review pass caught and fixed a genuine bug before commit: the
uncaught-exception closure originally captured the previous handler as a local,
which cannot convert to a @convention(c) function pointer; it is now a static.
The review also flagged a PRE-EXISTING discrepancy (project.yml declares
deploymentTarget.iOS 15.0, but the existing SettingsView already uses
NavigationStack + .topBarTrailing, iOS 16/17); the new code follows the file's
established pattern and does not change the target — reconciling it is flagged as a
v2.0.7 item.

Version bump: workspace 2.0.5 -> 2.0.6; iOS MARKETING_VERSION -> 2.0.6.

Verification (host gates all green): cargo check --workspace (version-only lock
cascade); fmt; clippy --workspace --all-targets -D warnings (no issues);
RUSTDOCFLAGS=-D warnings cargo doc; the thumbv7em no_std cross-compile; both wasm32
clippy gates; markdownlint (pinned v0.39.0); Localizable.xcstrings JSON validity.
Core untouched -> the determinism / AccuracyCoin 141/141 contract is unaffected.
The Swift compile + on-device crash-capture verification remain the documented
macOS / TestFlight carryover (flagged for v2.0.9).

Docs: CHANGELOG [2.0.6]; docs/STATUS.md current-release block (chain demoted);
to-dos/ROADMAP.md; .github/release-notes/v2.0.6.md; docs/ios.md (the Parity
section); new docs/ios-v2.0.6-readiness.md.

TestFlight-only; App Store + AltStore PAL stay deferred to the v2.1.0 joint launch.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 9, 2026 05:55

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request updates RustyNES to version 2.0.6 ("Parity"), introducing an opt-in, local-only crash reporting feature for iOS along with updated documentation and localization. Feedback on the implementation focuses on critical thread-safety and robustness improvements in CrashReporter.swift to comply with Swift 6 strict concurrency, such as caching device metadata and the log directory at launch to avoid unsafe operations and @MainActor isolation violations inside the uncaught exception handler. Additionally, a performance issue was identified in SettingsView.swift where synchronous disk I/O in a computed property within the SwiftUI body could block the main thread, with a recommendation to load the log contents using @State and .onAppear instead.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread ios/RustyNES/CrashReporter.swift
Comment thread ios/RustyNES/CrashReporter.swift
Comment thread ios/RustyNES/CrashReporter.swift Outdated
Comment thread ios/RustyNES/CrashReporter.swift Outdated
Comment thread ios/RustyNES/SettingsView.swift

Copilot AI 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.

Pull request overview

This PR cuts the v2.0.6 “Harbor” release for the iOS finalization train by adding an opt-in, local-only crash-reporting surface (NSException-only) and updating the iOS UI + docs/release metadata to match.

Changes:

  • Adds iOS crash reporting via a process-wide uncaught-NSException handler that writes local crash logs, plus a Settings UI to view/copy/clear logs.
  • Adds EN/ES localization entries for the new Diagnostics/crash-log UI.
  • Updates release/version artifacts and documentation for v2.0.6 (workspace + iOS MARKETING_VERSION, changelog, status/readiness docs, release notes, roadmap).

Reviewed changes

Copilot reviewed 12 out of 13 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
to-dos/ROADMAP.md Updates “Current release” narrative to v2.0.6 and shifts v2.0.5 to “Preceding release”.
ios/RustyNES/SettingsView.swift Adds Diagnostics section + crash log viewer/detail UI and clipboard copy action.
ios/RustyNES/Localizable.xcstrings Adds new localized strings for crash-reporting UI.
ios/RustyNES/CrashReporter.swift Introduces opt-in NSException handler and local log persistence/pruning logic.
ios/RustyNES/AppModel.swift Persists crash-reporting opt-in, installs handler at launch and on enable.
ios/project.yml Bumps iOS MARKETING_VERSION to 2.0.6.
docs/STATUS.md Updates current release banner/details to v2.0.6.
docs/ios.md Documents v2.0.6 parity work and crash-reporting surface.
docs/ios-v2.0.6-readiness.md Adds v2.0.6 readiness record and verification notes.
CHANGELOG.md Adds [2.0.6] entry describing iOS-only changes and verification posture.
Cargo.toml Bumps workspace version to 2.0.6.
Cargo.lock Updates crate versions to 2.0.6 (version-only cascade).
.github/release-notes/v2.0.6.md Adds release notes for v2.0.6.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread ios/RustyNES/CrashReporter.swift Outdated
Comment thread ios/RustyNES/SettingsView.swift Outdated
doublegate and others added 2 commits July 9, 2026 02:05
… off-main log read)

Adopt all five Gemini review comments on PR #245 — genuine crash-handler
robustness + a real actor-isolation fix:

- @MainActor-isolation violation (the important one): `UIDevice.current` is
  main-actor-isolated, so reading `.model` / `.systemVersion` inside `writeLog`,
  which runs from the C uncaught-exception handler on an arbitrary thread, was an
  isolation violation (and unsafe in an unstable post-crash process). Fixed by
  caching device / OS / app-version metadata AND the resolved log-directory URL as
  plain-value statics, populated in `install(enabled:)` — now `@MainActor` — before
  the handler is installed. `install` is only ever called from the `@MainActor`
  `AppModel` (init + the toggle didSet), so the synchronous @mainactor call is valid.

- Unsafe filesystem work in the handler: log pruning (directory enumeration +
  deletion) moved out of `writeLog` to `install()` (launch, main thread). The
  handler now does the bare minimum — format one string, write one file — touching
  only cached statics + the pre-resolved directory (no `UIDevice.current`, no
  `Bundle.main.infoDictionary`, no directory creation, no enumeration).

- Synchronous disk I/O in a SwiftUI computed property: `CrashLogDetailView.text`
  was read from disk on every `body` evaluation. Now loaded once off the main
  thread via `.task` + `Task.detached` into `@State` (the `Sendable` `URL` is
  bound locally so `self` is never captured), then published back on the main actor.

An independent Swift re-review confirms the rewrite is clean: the @mainactor split
removes the would-be `UIDevice.current` isolation violation from the crash handler,
the `@convention(c)` closure still captures nothing (only statics), `writeLog`
stays nonisolated so it remains callable from the C callback, and the
`.task`/`Task.detached` load is race-free. (Standing caveat, not an error today:
the nonisolated static-var access would need `nonisolated(unsafe)` only if the
module ever moves to the Swift 6 language mode; the codebase is Swift 5.)

Readiness doc updated with the crash-handler-discipline rationale.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…fallback (Copilot review)

Adopt Copilot's two review comments on PR #245:

- Privacy: the crash-log directory under Application Support was not excluded from
  iCloud / device backups, so iOS could include the local logs in a backup —
  undermining the "nothing leaves the device" posture. `resolveLogDir()` now sets
  `URLResourceValues.isExcludedFromBackup = true` on the directory after creating
  it; the logs are ephemeral local diagnostics, so excluding them keeps them
  on-device even through a full backup / restore.

- i18n: the crash-log detail view's unreadable-file fallback was a hard-coded
  English "(unreadable)". It is now `String(localized: "(unreadable)")` with an ES
  translation ("(ilegible)") added to Localizable.xcstrings, consistent with the
  EN + ES coverage the release advertises.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@doublegate
doublegate merged commit ade0084 into main Jul 9, 2026
23 checks passed
@doublegate
doublegate deleted the feat/v2.0.6-ios-parity branch July 9, 2026 06:32
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