Skip to content

Fix appearance override across app restarts#1492

Merged
cyanzhong merged 1 commit into
MarkEdit-app:mainfrom
bcantoni:fix-dark-mode-issue
Jun 17, 2026
Merged

Fix appearance override across app restarts#1492
cyanzhong merged 1 commit into
MarkEdit-app:mainfrom
bcantoni:fix-dark-mode-issue

Conversation

@bcantoni

Copy link
Copy Markdown
Contributor

This is a potential fix for issue #1491 where manually setting the dark/light mode appearance does not 'stick' across app restarts.

Claude Code analyzed the problem and created the fix here to move NSApp.appearance setup to applicationWillFinishLaunching, before warmUp() and before macOS can restore any windows:

Checklist

  • I have followed the development guide and matched the existing code style
  • I have performed a self-review of my own code, kept the diff minimal, and avoided unrelated changes
  • I have smoke tested the change, covering the main affected paths
  • For user-visible strings: localization keys have been added/updated as needed
  • For UI changes: I have attached before/after screenshots or a short screen recording

I created a local build with my change and have tested on two different Mac laptops and it seems to fix the issue.

This is a potential fix for issue MarkEdit-app#1491 where manually setting the dark/light mode
appearance does not 'stick' across app restarts.

Claude Code analyzed the problem and created the fix here to move `NSApp.appearance`
setup to `applicationWillFinishLaunching`, **before** `warmUp()` and before macOS
can restore any windows:
@bcantoni

Copy link
Copy Markdown
Contributor Author

Full disclosure I am not too familiar with Swift. I used Claude Code to diagnose this issue and propose a fix which, in my local testing, seems correct.

I included a short note in the commit message, but here's the longer explanation from Claude.


Symptom

Setting Appearance → Dark in General Settings works for the current session, but after quit and reopen the app randomly reverts to the system (light) appearance.

Root cause

A race condition in the app launch sequence.

The relevant call sites:

applicationWillFinishLaunching  →  EditorPreloader.shared.warmUp()
                                       ↳ creates EditorViewController (starts WebView load)

[macOS restores previous windows]
    → viewWillAppear()
        → configureToolbar()
            → view.window?.appearance = AppTheme.current.resolvedAppearance
               (AppTheme.current reads NSApp.effectiveAppearance)

applicationDidFinishLaunching   →  NSApp.appearance = AppPreferences.General.appearance.resolved()
                                   appearanceObservation = NSApp.observe(\.effectiveAppearance) { … }

The problem: NSApp.appearance was set in applicationDidFinishLaunching, but macOS window restoration can trigger viewWillAppear() / configureToolbar() before that point. When configureToolbar() runs early, NSApp.appearance is still nil, so NSApp.effectiveAppearance reflects the system appearance (light). The window's own .appearance property is then explicitly set to .aqua (light).

When applicationDidFinishLaunching later sets NSApp.appearance = .darkAqua, windows that already have an explicit per-window .appearance override do not inherit the app-level change — they stay light.

The appearanceObservation KVO is also registered after the initial NSApp.appearance assignment, so it never fires to correct the already-visible windows.

The "randomness" comes from whether window restoration races ahead of applicationDidFinishLaunching — which varies with system load, WebView startup speed, and how quickly the preloaded controller is ready.

Fix

Move NSApp.appearance setup to applicationWillFinishLaunching, before warmUp() and before macOS can restore any windows:

// AppDelegate.swift

func applicationWillFinishLaunching(_ notification: Notification) {
    NSApp.appearance = AppPreferences.General.appearance.resolved()  // moved here
    EditorPreloader.shared.warmUp()
}

func applicationDidFinishLaunching(_ notification: Notification) {
    // NSApp.appearance line removed from here
    appearanceObservation = NSApp.observe(\.effectiveAppearance) { _, _ in
        
    }
    
}

By the time window restoration and configureToolbar() run, NSApp.effectiveAppearance already reflects the user's preference, so view.window?.appearance is set correctly from the start. The appearanceObservation remains in applicationDidFinishLaunching — it only needs to handle subsequent in-session appearance changes (e.g., system-wide dark/light toggle), not the initial launch.

@cyanzhong

Copy link
Copy Markdown
Contributor

Thank you for your contribution. While I was not able to reproduce this locally, I think the root cause analysis makes sense.

Since this works for you and looks harmless to others, I am checking it in.

@cyanzhong cyanzhong changed the title Fix dark/light mode appearance across app restarts Fix appearance override across app restarts Jun 17, 2026
@cyanzhong cyanzhong merged commit 551b71c into MarkEdit-app:main Jun 17, 2026
1 check 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