Skip to content

Modernize Glance baseline - #10

Merged
ranokay merged 1 commit into
mainfrom
modernization-checkup
Jul 5, 2026
Merged

Modernize Glance baseline#10
ranokay merged 1 commit into
mainfrom
modernization-checkup

Conversation

@ranokay

@ranokay ranokay commented Jul 4, 2026

Copy link
Copy Markdown
Owner

Summary

  • update Glance baseline for macOS 15+/arm64-only assumptions
  • fix Swift 6 actor-isolation build issues on the current toolchain
  • refresh safe SwiftPM/Go dependency pins and align docs/tooling
  • preserve existing file support while trimming obsolete Jupyter font fallbacks

Validation

  • mise run test:go
  • mise run test:xcode
  • mise run test
  • mise run build
  • git diff --check
  • govulncheck ./... in HTMLConverter
  • lipo -info confirmed arm64-only Glance, QLPlugin, and htmlconverter.a

Notes

  • The earlier local CoreSimulator version mismatch was resolved by refreshing the stale CoreSimulator service with xcrun simctl list devices; mise run test:xcode no longer emits that mismatch.

Copilot AI review requested due to automatic review settings July 4, 2026 21:45
@coderabbitai

coderabbitai Bot commented Jul 4, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

This PR shifts the project to Apple Silicon-only builds targeting macOS 15 and Swift 6, updating CI to assert/verify arm64-only artifacts, converting bundled KaTeX fonts to woff2-only, and adding MainActor/nonisolated/Sendable annotations across app and plugin code for Swift 6 concurrency compliance.

Changes

Apple Silicon-only migration

Layer / File(s) Summary
CI arm64 assertion and verification
.github/workflows/release.yml
Release workflow asserts arm64 runner, installs arm64-only mise, and verifies built artifacts contain no x86_64 Mach-O code.
Xcode build settings and HTMLConverter script
Glance.xcodeproj/project.pbxproj
Build phases updated to produce single arm64 htmlconverter.a, set ARCHS=arm64, bump SWIFT_VERSION to 6.0, raise deployment target to macOS 15.0, update compatibility metadata, bump SWCompression version.
Dependency lockfile and tooling
.../Package.resolved, HTMLConverter/go.mod, mise.toml, .gitignore, .swiftformat
Package pins regenerated, Go dependencies bumped, mise Go version bumped, clean task simplified, and ignore/format configs updated for the new layout.
README platform requirement updates
README.md
Documentation updated to state Apple Silicon-only builds and macOS 15+ requirements.

KaTeX woff2-only font packaging

Layer / File(s) Summary
Font resource registration and stylesheet
Glance.xcodeproj/project.pbxproj, QLPlugin/Resources/jupyter/jupyter-katex.min.css
KaTeX font entries and resource lists reduced to woff2-only, and font-face rules updated to reference only woff2 sources.
Smoke test for woff2-only fonts
GlanceTests/PreviewSmokeTests.swift
New test verifies stylesheet KaTeX references are woff2-only and confirms bundled fonts exist with no ttf/woff fallbacks.

Swift concurrency adoption

Layer / File(s) Summary
AppDelegate and settings actor isolation
Glance/AppDelegate.swift, Glance/SettingsWC.swift, Glance/Shared/Utils/AppSettings.swift
AppDelegate gains @MainActor, activation calls simplified, dock update deferred via MainActor Task, and settings singleton marked nonisolated(unsafe).
Extensions and menu classes
Glance/Extensions/NSMenuItem.swift, Glance/Extensions/URL.swift, Glance/Utils/Menu.swift
Cocoa imports and @MainActor annotations added to extensions and Interface Builder menu-item subclasses.
QLPlugin concurrency contracts
QLPlugin/Extensions/String.swift, QLPlugin/MainVC.swift, QLPlugin/Views/PreviewVC.swift, QLPlugin/Views/PreviewVCs/WebPreviewVC.swift
Regex cache and resourceBundle marked nonisolated(unsafe)/nonisolated, completion handler made @Sendable, protocol method made @MainActor, deinit cleanup wrapped in MainActor.assumeIsolated.
Test synchronization refactor
GlanceTests/PreviewSmokeTests.swift
Temp directory property marked nonisolated(unsafe) and WebKit wait helpers switched to deadline-based RunLoop polling.

Estimated code review effort: 4 (Complex) | ~55 minutes

Possibly related PRs

  • ranokay/glance#3: Both PRs modify the HTMLConverter build pipeline shell script in project.pbxproj for building htmlconverter.a.
  • ranokay/glance#5: Both PRs modify the same HTMLConverter build phase script, one moving to arm64-only, the other adding dual-arch lipo builds.
  • ranokay/glance#8: Overlaps on AppSettingsStore.sharedDefaults concurrency changes and related AppDelegate/QLPlugin isolation adjustments.

Poem

A hop, a build, an arm64 cheer,
No x86 shadows lurking near.
Fonts trimmed to woff2, light as air,
MainActor watches with rabbit-care.
🐇 Thump thump — ship it, all is clear!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Title check ❓ Inconclusive The title is relevant, but it is broad and doesn’t clearly signal the main arm64/macOS 15 modernization and tooling changes. Make the title more specific, e.g. mention the macOS 15+/arm64 modernization or the Swift 6 baseline update.
✅ Passed checks (4 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch modernization-checkup

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

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

Modernizes the Glance fork’s baseline to macOS 15+ and Apple Silicon-only, updates Swift concurrency annotations for Swift 6, refreshes dependency pins, and trims obsolete KaTeX/Jupyter font fallbacks while keeping existing preview support.

Changes:

  • Raise baseline assumptions (macOS 15+, arm64-only) across docs, build scripts, Xcode project settings, and release workflow verification.
  • Address Swift 6 actor-isolation constraints via @MainActor/nonisolated/@Sendable adjustments in app/plugin code and tests.
  • Refresh dependency locks/pins (SwiftPM + Go modules) and remove non-WOFF2 KaTeX font fallbacks (plus add a regression test).

Reviewed changes

Copilot reviewed 21 out of 63 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
README.md Updates stated macOS/architecture support and rationale.
QLPlugin/Views/PreviewVCs/WebPreviewVC.swift Swift 6 isolation tweaks; adjusts resource bundle access and teardown logic.
QLPlugin/Views/PreviewVC.swift Marks preview creation API as @MainActor to align with UI-thread expectations.
QLPlugin/Resources/jupyter/jupyter-katex.min.css Removes fallback .woff/.ttf font references, leaving .woff2 only.
QLPlugin/MainVC.swift Updates Quick Look entrypoint signature for Swift 6 (nonisolated, @Sendable).
QLPlugin/Extensions/String.swift Makes regex cache nonisolated(unsafe) for Swift 6 concurrency checking.
mise.toml Bumps Go toolchain pin and simplifies HTMLConverter clean patterns.
HTMLConverter/go.mod Updates Go dependency versions.
HTMLConverter/go.sum Updates Go dependency checksums for the new pins.
GlanceTests/PreviewSmokeTests.swift Adds KaTeX font reference regression test; adjusts isolation and webview wait helpers.
Glance/Utils/Menu.swift Adds @MainActor to menu item subclasses for actor-isolation correctness.
Glance/Shared/Utils/AppSettings.swift Marks shared UserDefaults accessors nonisolated(unsafe) for Swift 6 checking.
Glance/SettingsWC.swift Uses NSApp.activate() for activation behavior under the new baseline.
Glance/Extensions/URL.swift Removes redundant import cleanup.
Glance/Extensions/NSMenuItem.swift Adds @MainActor isolation to menu item extension/associated keys.
Glance/AppDelegate.swift Marks app delegate @MainActor, updates activation calls, and uses Task { @MainActor … } deferral.
Glance.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved Adds SwiftPM lockfile to repo for deterministic resolution.
Glance.xcodeproj/project.pbxproj Sets Swift 6, arm64-only ARCHS, macOS 15 deployment target, updates build phases/resources and package requirements.
.woodpecker/test.yaml Removes Woodpecker test pipeline config.
.woodpecker/lint.yaml Removes Woodpecker lint pipeline config.
.swiftformat Updates Swift version to 6.0 and excludes build/.
.gitignore Un-ignores the committed SwiftPM lockfile path within the workspace.
.github/workflows/release.yml Enforces Apple Silicon runner and verifies produced artifacts are arm64-only.

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

Comment on lines 63 to +67
deinit {
webView?.navigationDelegate = nil
webView?.stopLoading()
MainActor.assumeIsolated {
webView?.navigationDelegate = nil
webView?.stopLoading()
}
Comment on lines +5 to 8
@MainActor
final class PreviewSmokeTests: XCTestCase {
private var temporaryDirectory: URL!
nonisolated(unsafe) private var temporaryDirectory: URL!

@coderabbitai coderabbitai 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.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
QLPlugin/MainVC.swift (1)

45-104: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Bridge the DispatchQueue.main.async body into MainActor
preparePreviewOfFile is nonisolated, but this closure still touches self.previewFile(file:) and self.maxFileSize. Under Swift 6, wrap the body in MainActor.assumeIsolated { ... } or switch to Task { @mainactor in ... }.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@QLPlugin/MainVC.swift` around lines 45 - 104, The body of
preparePreviewOfFile still accesses MainActor-isolated state from a nonisolated
context inside DispatchQueue.main.async, which will trip Swift 6 isolation
checks. Move the entire preview logic into a MainActor context by wrapping the
closure body in MainActor.assumeIsolated { ... } or replacing the dispatch with
Task { `@MainActor` in ... }, and keep the existing uses of self.maxFileSize and
self.previewFile(file:) inside that isolated block.
🧹 Nitpick comments (1)
QLPlugin/Views/PreviewVC.swift (1)

9-9: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Move @MainActor to its own line to satisfy SwiftLint.

SwiftLint's attributes rule requires attributes on their own line for function declarations.

♻️ Proposed style fix
 protocol Preview {
 	init()
-	`@MainActor` func createPreviewVC(file: File) throws -> PreviewVC
+	`@MainActor`
+	func createPreviewVC(file: File) throws -> PreviewVC
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@QLPlugin/Views/PreviewVC.swift` at line 9, Move the `@MainActor` attribute used
on createPreviewVC(file:) to its own line so the function declaration conforms
to SwiftLint’s attributes rule. Update the PreviewVC-related declaration in
PreviewVC.swift so the attribute is separated from the func signature, keeping
the same annotation and behavior while matching the required formatting.

Source: Linters/SAST tools

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@QLPlugin/Extensions/String.swift`:
- Line 5: The static regexCache declaration in String.swift needs its Swift
modifiers reordered to satisfy modifier_order. Update the declaration so
nonisolated(unsafe) appears before private on the regexCache property, keeping
the same NSCache<NSString, NSRegularExpression> definition.

In `@QLPlugin/Views/PreviewVCs/WebPreviewVC.swift`:
- Around line 63-68: The WebKit teardown in WebPreviewVC’s deinit is using
MainActor.assumeIsolated, which can trap if the final release happens off the
main thread. Move the cleanup for webView (navigationDelegate reset and
stopLoading) out of deinit into an explicit main-actor invalidation method, or
make the deinit isolated if the deployment target allows it, so teardown is
guaranteed to run on the main actor.

---

Outside diff comments:
In `@QLPlugin/MainVC.swift`:
- Around line 45-104: The body of preparePreviewOfFile still accesses
MainActor-isolated state from a nonisolated context inside
DispatchQueue.main.async, which will trip Swift 6 isolation checks. Move the
entire preview logic into a MainActor context by wrapping the closure body in
MainActor.assumeIsolated { ... } or replacing the dispatch with Task {
`@MainActor` in ... }, and keep the existing uses of self.maxFileSize and
self.previewFile(file:) inside that isolated block.

---

Nitpick comments:
In `@QLPlugin/Views/PreviewVC.swift`:
- Line 9: Move the `@MainActor` attribute used on createPreviewVC(file:) to its
own line so the function declaration conforms to SwiftLint’s attributes rule.
Update the PreviewVC-related declaration in PreviewVC.swift so the attribute is
separated from the func signature, keeping the same annotation and behavior
while matching the required formatting.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: f3c30458-97ac-400d-9f16-3690f01e8bec

📥 Commits

Reviewing files that changed from the base of the PR and between 57fcd32 and d6236ea.

⛔ Files ignored due to path filters (41)
  • HTMLConverter/go.sum is excluded by !**/*.sum
  • QLPlugin/Resources/jupyter/fonts/KaTeX_AMS-Regular.ttf is excluded by !**/*.ttf
  • QLPlugin/Resources/jupyter/fonts/KaTeX_AMS-Regular.woff is excluded by !**/*.woff
  • QLPlugin/Resources/jupyter/fonts/KaTeX_Caligraphic-Bold.ttf is excluded by !**/*.ttf
  • QLPlugin/Resources/jupyter/fonts/KaTeX_Caligraphic-Bold.woff is excluded by !**/*.woff
  • QLPlugin/Resources/jupyter/fonts/KaTeX_Caligraphic-Regular.ttf is excluded by !**/*.ttf
  • QLPlugin/Resources/jupyter/fonts/KaTeX_Caligraphic-Regular.woff is excluded by !**/*.woff
  • QLPlugin/Resources/jupyter/fonts/KaTeX_Fraktur-Bold.ttf is excluded by !**/*.ttf
  • QLPlugin/Resources/jupyter/fonts/KaTeX_Fraktur-Bold.woff is excluded by !**/*.woff
  • QLPlugin/Resources/jupyter/fonts/KaTeX_Fraktur-Regular.ttf is excluded by !**/*.ttf
  • QLPlugin/Resources/jupyter/fonts/KaTeX_Fraktur-Regular.woff is excluded by !**/*.woff
  • QLPlugin/Resources/jupyter/fonts/KaTeX_Main-Bold.ttf is excluded by !**/*.ttf
  • QLPlugin/Resources/jupyter/fonts/KaTeX_Main-Bold.woff is excluded by !**/*.woff
  • QLPlugin/Resources/jupyter/fonts/KaTeX_Main-BoldItalic.ttf is excluded by !**/*.ttf
  • QLPlugin/Resources/jupyter/fonts/KaTeX_Main-BoldItalic.woff is excluded by !**/*.woff
  • QLPlugin/Resources/jupyter/fonts/KaTeX_Main-Italic.ttf is excluded by !**/*.ttf
  • QLPlugin/Resources/jupyter/fonts/KaTeX_Main-Italic.woff is excluded by !**/*.woff
  • QLPlugin/Resources/jupyter/fonts/KaTeX_Main-Regular.ttf is excluded by !**/*.ttf
  • QLPlugin/Resources/jupyter/fonts/KaTeX_Main-Regular.woff is excluded by !**/*.woff
  • QLPlugin/Resources/jupyter/fonts/KaTeX_Math-BoldItalic.ttf is excluded by !**/*.ttf
  • QLPlugin/Resources/jupyter/fonts/KaTeX_Math-BoldItalic.woff is excluded by !**/*.woff
  • QLPlugin/Resources/jupyter/fonts/KaTeX_Math-Italic.ttf is excluded by !**/*.ttf
  • QLPlugin/Resources/jupyter/fonts/KaTeX_Math-Italic.woff is excluded by !**/*.woff
  • QLPlugin/Resources/jupyter/fonts/KaTeX_SansSerif-Bold.ttf is excluded by !**/*.ttf
  • QLPlugin/Resources/jupyter/fonts/KaTeX_SansSerif-Bold.woff is excluded by !**/*.woff
  • QLPlugin/Resources/jupyter/fonts/KaTeX_SansSerif-Italic.ttf is excluded by !**/*.ttf
  • QLPlugin/Resources/jupyter/fonts/KaTeX_SansSerif-Italic.woff is excluded by !**/*.woff
  • QLPlugin/Resources/jupyter/fonts/KaTeX_SansSerif-Regular.ttf is excluded by !**/*.ttf
  • QLPlugin/Resources/jupyter/fonts/KaTeX_SansSerif-Regular.woff is excluded by !**/*.woff
  • QLPlugin/Resources/jupyter/fonts/KaTeX_Script-Regular.ttf is excluded by !**/*.ttf
  • QLPlugin/Resources/jupyter/fonts/KaTeX_Script-Regular.woff is excluded by !**/*.woff
  • QLPlugin/Resources/jupyter/fonts/KaTeX_Size1-Regular.ttf is excluded by !**/*.ttf
  • QLPlugin/Resources/jupyter/fonts/KaTeX_Size1-Regular.woff is excluded by !**/*.woff
  • QLPlugin/Resources/jupyter/fonts/KaTeX_Size2-Regular.ttf is excluded by !**/*.ttf
  • QLPlugin/Resources/jupyter/fonts/KaTeX_Size2-Regular.woff is excluded by !**/*.woff
  • QLPlugin/Resources/jupyter/fonts/KaTeX_Size3-Regular.ttf is excluded by !**/*.ttf
  • QLPlugin/Resources/jupyter/fonts/KaTeX_Size3-Regular.woff is excluded by !**/*.woff
  • QLPlugin/Resources/jupyter/fonts/KaTeX_Size4-Regular.ttf is excluded by !**/*.ttf
  • QLPlugin/Resources/jupyter/fonts/KaTeX_Size4-Regular.woff is excluded by !**/*.woff
  • QLPlugin/Resources/jupyter/fonts/KaTeX_Typewriter-Regular.ttf is excluded by !**/*.ttf
  • QLPlugin/Resources/jupyter/fonts/KaTeX_Typewriter-Regular.woff is excluded by !**/*.woff
📒 Files selected for processing (22)
  • .github/workflows/release.yml
  • .gitignore
  • .swiftformat
  • .woodpecker/lint.yaml
  • .woodpecker/test.yaml
  • Glance.xcodeproj/project.pbxproj
  • Glance.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
  • Glance/AppDelegate.swift
  • Glance/Extensions/NSMenuItem.swift
  • Glance/Extensions/URL.swift
  • Glance/SettingsWC.swift
  • Glance/Shared/Utils/AppSettings.swift
  • Glance/Utils/Menu.swift
  • GlanceTests/PreviewSmokeTests.swift
  • HTMLConverter/go.mod
  • QLPlugin/Extensions/String.swift
  • QLPlugin/MainVC.swift
  • QLPlugin/Resources/jupyter/jupyter-katex.min.css
  • QLPlugin/Views/PreviewVC.swift
  • QLPlugin/Views/PreviewVCs/WebPreviewVC.swift
  • README.md
  • mise.toml
💤 Files with no reviewable changes (3)
  • .woodpecker/lint.yaml
  • .woodpecker/test.yaml
  • Glance/Extensions/URL.swift

extension String {
/// Cache for compiled regular expressions to avoid recompilation on every call
private static let regexCache = NSCache<NSString, NSRegularExpression>()
private nonisolated(unsafe) static let regexCache = NSCache<NSString, NSRegularExpression>()

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Reorder modifiers to satisfy modifier_order.

SwiftLint flags that nonisolated should precede private.

🧹 Proposed fix
-	private nonisolated(unsafe) static let regexCache = NSCache<NSString, NSRegularExpression>()
+	nonisolated(unsafe) private static let regexCache = NSCache<NSString, NSRegularExpression>()
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
private nonisolated(unsafe) static let regexCache = NSCache<NSString, NSRegularExpression>()
nonisolated(unsafe) private static let regexCache = NSCache<NSString, NSRegularExpression>()
🧰 Tools
🪛 SwiftLint (0.65.0)

[Warning] 5-5: nonisolated modifier should come before private

(modifier_order)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@QLPlugin/Extensions/String.swift` at line 5, The static regexCache
declaration in String.swift needs its Swift modifiers reordered to satisfy
modifier_order. Update the declaration so nonisolated(unsafe) appears before
private on the regexCache property, keeping the same NSCache<NSString,
NSRegularExpression> definition.

Source: Linters/SAST tools

Comment on lines 63 to 68
deinit {
webView?.navigationDelegate = nil
webView?.stopLoading()
MainActor.assumeIsolated {
webView?.navigationDelegate = nil
webView?.stopLoading()
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== File map ==\n'
git ls-files 'QLPlugin/Views/PreviewVCs/*' 'QLPlugin/**/WebPreviewVC.swift' 'QLPlugin/**/PreviewVC*.swift' 'QLPlugin/**/QuickLook*' | sed 's#^`#-` #'

printf '\n== WebPreviewVC outline ==\n'
ast-grep outline QLPlugin/Views/PreviewVCs/WebPreviewVC.swift --view expanded || true

printf '\n== WebPreviewVC source (relevant slice) ==\n'
cat -n QLPlugin/Views/PreviewVCs/WebPreviewVC.swift | sed -n '1,220p'

printf '\n== Search for MainActor / deinit / WebPreviewVC usage ==\n'
rg -n "`@MainActor`|deinit|WebPreviewVC|NSViewController" QLPlugin -g '!**/DerivedData/**' || true

Repository: ranokay/glance

Length of output: 9639


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== PreviewVC protocol ==\n'
cat -n QLPlugin/Views/PreviewVC.swift

printf '\n== MainVC source (relevant slice) ==\n'
ast-grep outline QLPlugin/MainVC.swift --view expanded || true
cat -n QLPlugin/MainVC.swift | sed -n '1,220p'

printf '\n== Search for `@MainActor` on preview controller creation and other deinit patterns ==\n'
rg -n "`@MainActor`|deinit \{|DispatchQueue\.main|assumeIsolated" QLPlugin/Views QLPlugin/MainVC.swift || true

Repository: ranokay/glance

Length of output: 6209


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== Preview implementations ==\n'
rg -n "func createPreviewVC|class .*Preview|struct .*Preview|`@MainActor` func createPreviewVC" QLPlugin/Views/Previews QLPlugin/Views -g '*.swift' || true

printf '\n== Relevant createPreviewVC bodies ==\n'
for f in QLPlugin/Views/Previews/*.swift; do
  echo "--- $f ---"
  ast-grep outline "$f" --view expanded || true
done

Repository: ranokay/glance

Length of output: 9835


🌐 Web query:

Swift MainActor.assumeIsolated deinit nonisolated final release off main thread documentation

💡 Result:

In Swift, the deinitializer (deinit) of an actor or a global-actor isolated class (such as one annotated with @MainActor) is not guaranteed to execute on that actor's executor [1][2][3]. This is because the final release of an object can occur on any thread, depending on where the last strong reference is held [1][4]. If you use MainActor.assumeIsolated within a deinit, you are asserting that the current thread is already the MainActor [5]. If the deinit is triggered on a background thread, this assertion will fail and result in a runtime crash [1][5]. Therefore, it is strongly recommended not to use MainActor.assumeIsolated in deinit as a way to perform cleanup [1]. Key concepts and best practices regarding this behavior include: Isolated Deinitializers: Swift 5.10+ and later versions support isolated deinitializers (SE-0371), which allow the deinit body to be scheduled on the required actor's executor if necessary [4][6]. This is the modern, safe way to handle cleanup for isolated types. By default, unless specified, deinitializers are non-isolated [4]. Non-isolated deinit and cleanup: Because deinit is non-isolated by default, it cannot safely access isolated state or properties of the actor [4][3]. If you need to perform cleanup that requires MainActor isolation: 1. Prefer Isolated Deinitializers: If your environment supports it, ensure your deinit is treated as isolated, which handles the executor hopping automatically [4][6]. 2. Avoid using DispatchQueue.main.async: Attempting to "fix" a non-isolated deinit by dispatching to the main queue (e.g., using DispatchQueue.main.async) is unsafe because the object is already being deallocated [1]. The object might be destroyed before the block executes, leading to crashes or access to invalid memory [1]. 3. Manual Cleanup: If you cannot use isolated deinitializers, the recommended pattern is to expose an explicit cleanup method (e.g., stop, invalidate) that is called before the object is expected to be deallocated [7]. If you have code that triggers a compiler error because you are accessing isolated properties in a non-isolated deinit, it is a signal that your cleanup logic needs to be moved to an explicitly called method or that the type should be refactored to handle its own resource management [7][3]. Use of nonisolated(unsafe) properties or other workarounds to suppress these errors is generally discouraged as it risks data races [2][7].

Citations:


Avoid MainActor.assumeIsolated in deinit deinit is nonisolated, so this cleanup can still trap if the last release happens off-main. DispatchQueue.main.async is not a safe fallback during teardown; move this WebKit cleanup into an explicit main-actor invalidation path, or use an isolated deinit if the deployment target supports it.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@QLPlugin/Views/PreviewVCs/WebPreviewVC.swift` around lines 63 - 68, The
WebKit teardown in WebPreviewVC’s deinit is using MainActor.assumeIsolated,
which can trap if the final release happens off the main thread. Move the
cleanup for webView (navigationDelegate reset and stopLoading) out of deinit
into an explicit main-actor invalidation method, or make the deinit isolated if
the deployment target allows it, so teardown is guaranteed to run on the main
actor.

@ranokay
ranokay merged commit 46dae46 into main Jul 5, 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