Skip to content

Save application state before macOS update installation - #1004

Merged
trypsynth merged 3 commits into
trypsynth:masterfrom
johanntan:macos-update-shutdown
Oct 3, 2026
Merged

trypsynth merged 3 commits into
trypsynth:masterfrom
johanntan:macos-update-shutdown

Conversation

@johanntan

@johanntan johanntan commented Oct 2, 2026 •

Copy link
Copy Markdown
Contributor

Problem and behavior

The automatic macOS installer proposed in trypsynth/ship-shape#6 needs Paperback to exit after preparation. Its default immediate-exit callback would skip Paperback's normal close handler and the state it saves. This integration closes Paperback through that handler before the helper replaces and relaunches the app.

  • Announce “Installing the update. Paperback will restart.” through the translated, high-priority live region. Disable the frame and keep the main event loop running for two seconds so macOS can deliver the asynchronous accessibility notification before teardown.
  • Force-close the main window on the UI thread to save window geometry, settings, the active document and document positions, and stop audio and timers. Forced close bypasses macOS's usual hide-on-close veto; then exit the application event loop.
  • Forward PAPERBACK_CONFIG_DIR only when it was explicitly present in the current process's environment, preserving its native value. Default configuration resolution remains in effect when it is unset.
  • Keep the existing update-check entry point on Windows and Linux.

Dependency and merge order

Draft: depends on ship-shape#6 being merged and released. The workspace currently patches ship-shape to the immutable reviewed commit bbbdc5761a54a9b0b742b0f393e28ae0548e895b on my fork, with a matching lockfile. This allows reviewers and CI to build the integration without a local sibling checkout.

Before merging:

  • Merge and release ship-shape#6.
  • Remove the temporary Git patch, bump the ship-shape crates.io dependency to that release, and regenerate Cargo.lock.
  • Rerun validation against the published dependency and perform a VoiceOver listening check during automatic installation.

Validation

On macOS, using the updated public pinned commit without a local override:

  • cargo check -p paperback passes.
  • cargo test -p paperback --bin paperback --locked: 116 tests pass.
  • cargo clippy -p paperback --all-targets --locked passes with existing repository warnings; formatting and whitespace checks pass.

The companion updater passes 64 automated tests and an additional read-only signer test against a notarized release, plus standalone strict clippy using its own lint policy.

Before the signer policy was tightened, a disposable Paperback copy completed a real Minisign-verified GitHub dev update: it quit, was replaced at the same test path, and relaunched with its isolated settings and document at the same visible passage. The resulting release passed code-signature verification and Gatekeeper assessment as notarized. Exact saved character-offset equality was not established. The new VoiceOver announcement has not yet been verified by a listening check on a developer-signed automatic-update build.

Automatic installation now requires the running and replacement apps to have the same Apple developer team; ad-hoc/unsigned builds retain manual installation. The helper allows 60 seconds for shutdown. A successful launch request cannot establish subsequent application health. The ship-shape README documents signer checks, manual fallback and recovery limits.

Use ship-shape's installer-ready callback to force-close the main window
through Paperback's normal persistence and cleanup handler, then leave the
application event loop. This saves settings, document positions and window
geometry, stops audio and timers, and bypasses macOS's hide-on-close veto
so the update helper can replace and relaunch the app.

Pass the resolved settings directory through Launch Services when the new
app is launched, preserving custom and isolated configurations.

Pin the companion ship-shape PR commit so this draft builds without a
local checkout. Replace the temporary Git patch with the published crate
version after trypsynth/ship-shape#6 is merged and released.

Co-Authored-By: Codex <noreply@openai.com>
@johanntan
johanntan force-pushed the macos-update-shutdown branch from a78e768 to 0ceefdb Compare October 2, 2026 14:18

@trypsynth trypsynth left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

One thing here, alongside my review of ship-shape#6: this always passes PAPERBACK_CONFIG_DIR to the relaunch, set to whatever folder config_dir() resolved to, even when the reader never set the variable. It works out to the same folder today, but it means every relaunch after an update runs as if an override had been given. Please only pass it on when PAPERBACK_CONFIG_DIR was actually set in this process's environment.

It would also help VoiceOver users to hear something before Paperback quits, something like "Installing the update. Paperback will restart." Otherwise the app just disappears and comes back a few seconds later.

Forward PAPERBACK_CONFIG_DIR only when it is present in this process's
environment. Preserve its native value so relaunch does not synthesize an
override for the default settings directory or lose non-UTF-8 path bytes.

Post a translated high-priority live-region announcement before updater
shutdown. Disable the frame and keep the UI event loop running for a brief
grace period so VoiceOver can receive the asynchronous notification, then
run the existing forced-close persistence and cleanup path.

Update the pinned ship-shape revision and lockfile to the reviewed signer,
stale-stage cleanup and log-retention fixes. Keep the integration draft
pending the upstream crate release.

Co-Authored-By: Codex <noreply@openai.com>
@johanntan

Copy link
Copy Markdown
Contributor Author

Pushed 5b54c1c with both changes:

  • PAPERBACK_CONFIG_DIR is forwarded only when it is actually present in this process's environment. An unset variable stays unset after relaunch, leaving the new app to resolve its default settings directory normally. The explicit value is forwarded as a native environment value, without converting non-UTF-8 paths to a lossy string.
  • Before updater shutdown, Paperback posts the translated high-priority announcement: “Installing the update. Paperback will restart.” The frame is disabled and the main event loop stays running for a two-second grace period, allowing macOS's asynchronous accessibility notification to be delivered before the window is destroyed. The existing forced-close handler then saves state, stops audio/timers and exits. The UI thread is not blocked during the delay.

The dependency pin and lockfile now point to ship-shape's review-fix commit bbbdc5761a54a9b0b742b0f393e28ae0548e895b, including the same-team signer requirement, abandoned-stage cleanup and success-log removal. This PR remains a draft until that change is merged and released.

Validation against that public pin: cargo check -p paperback, cargo clippy -p paperback --all-targets --locked, formatting and whitespace checks pass; 116 Paperback unit tests pass. Clippy still reports the existing repository warnings. I have not verified spoken delivery with VoiceOver enabled on a developer-signed automatic-update build; the asynchronous delivery and delayed shutdown path are implemented, but that listening check remains to be done.

The announcement is wrapped in patois::t for translation.

@trypsynth
trypsynth marked this pull request as ready for review October 3, 2026 22:29
@trypsynth
trypsynth merged commit 60b56e7 into trypsynth:master Oct 3, 2026
15 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