Ariver
2026-05-23 ffa76a42b2ffddb9271065af794281a170a4bd6f
Fix fullscreen Space overlay presentation
2 files modified
5 files renamed
127 ■■■■ changed files
Apptag/ApptagApp.swift 71 ●●●●● patch | view | raw | blame | history
Release/AppStore-7.6.0-20260523.2335/APP_STORE_CONNECT_METADATA.md patch | view | raw | blame | history
Release/AppStore-7.6.0-20260523.2335/BUILD_SIGN_UPLOAD.md 2 ●●● patch | view | raw | blame | history
Release/AppStore-7.6.0-20260523.2335/FIRST_TIME_MAC_APP_STORE_TODO.md patch | view | raw | blame | history
Release/AppStore-7.6.0-20260523.2335/QA_RELEASE_EVIDENCE.md 8 ●●●●● patch | view | raw | blame | history
Release/AppStore-7.6.0-20260523.2335/README.md 6 ●●●●● patch | view | raw | blame | history
Scripts/window_logic_qa.sh 40 ●●●●● patch | view | raw | blame | history
Apptag/ApptagApp.swift
@@ -258,13 +258,18 @@
        }
    }
    private func refreshLauncherChromeState(activate: Bool = false) {
    private func refreshLauncherChromeState(activate: Bool = false, avoidSpaceSwitch: Bool = false) {
        let showDock = UserDefaults.standard.bool(forKey: Self.showDockIconKey)
        lastShowDockIcon = showDock
        let desiredPolicy: NSApplication.ActivationPolicy = requiresForegroundOwnership
        let shouldStayAccessoryForCurrentFullscreenSpace = avoidSpaceSwitch
            && isOverlayVisible
            && !isSettingsVisible
        let desiredPolicy: NSApplication.ActivationPolicy = shouldStayAccessoryForCurrentFullscreenSpace
            ? .accessory
            : (requiresForegroundOwnership
            ? .regular
            : (showDock ? .regular : .accessory)
            : (showDock ? .regular : .accessory))
        if NSApp.activationPolicy() != desiredPolicy {
            NSApp.setActivationPolicy(desiredPolicy)
        }
@@ -274,7 +279,7 @@
            NSApp.presentationOptions = desiredPresentation
        }
        if activate && requiresForegroundOwnership {
        if activate && requiresForegroundOwnership && !shouldStayAccessoryForCurrentFullscreenSpace {
            let keyWindow = isSettingsVisible ? settingsWindow : (isOverlayVisible ? overlayWindow : nil)
            claimLauncherForeground(
                keyWindow: keyWindow,
@@ -749,10 +754,12 @@
    }
    private func showOrFocusOverlay(preferredScreen: NSScreen? = nil) {
        let placement = overlayPlacementContextForNextOverlay(preferredScreen: preferredScreen)
        let shouldAvoidSpaceSwitch = placement.map { hasFullscreenWindowOnScreen($0.screen) } ?? false
        if let overlayWindow, overlayWindow.isVisible {
            moveOverlayToCurrentPlacement(preferredScreen: preferredScreen)
            overlayWindow.level = currentOverlayLevel
            refreshLauncherChromeState(activate: true)
            refreshLauncherChromeState(activate: !shouldAvoidSpaceSwitch, avoidSpaceSwitch: shouldAvoidSpaceSwitch)
            overlayWindow.makeKeyAndOrderFront(nil)
            overlayWindow.orderFrontRegardless()
            if let settingsWindow, settingsWindow.isVisible {
@@ -778,7 +785,8 @@
    ) {
        guard let placement = overlayPlacementContextForNextOverlay(preferredScreen: preferredScreen) else { return }
        let shouldStageAsAccessory = shouldStageOverlayAsAccessory
        let shouldAvoidSpaceSwitch = hasFullscreenWindowOnScreen(placement.screen)
        let shouldStageAsAccessory = shouldStageOverlayAsAccessory || shouldAvoidSpaceSwitch
        if !stagedForAllSpaces && shouldStageAsAccessory {
            // Let the all-spaces panel attach to the pointer's display before the app reclaims focus.
@@ -831,7 +839,7 @@
        let placementFrame = placement.frame
        let finishForegroundClaim: () -> Void = { [weak self, weak window] in
            guard let self, let window, self.overlayWindow === window else { return }
            self.refreshLauncherChromeState(activate: true)
            self.refreshLauncherChromeState(activate: !shouldAvoidSpaceSwitch, avoidSpaceSwitch: shouldAvoidSpaceSwitch)
            if window.frame != placementFrame {
                window.setFrame(placementFrame, display: true)
                window.makeKeyAndOrderFront(nil)
@@ -848,6 +856,42 @@
        } else {
            finishForegroundClaim()
        }
    }
    private func hasFullscreenWindowOnScreen(_ screen: NSScreen) -> Bool {
        let screenFrame = screen.frame
        let windows = CGWindowListCopyWindowInfo([.optionOnScreenOnly, .excludeDesktopElements], kCGNullWindowID) as? [[String: Any]] ?? []
        return windows.contains { info in
            guard let owner = info[kCGWindowOwnerName as String] as? String,
                  owner != AppIdentity.displayName,
                  owner != "Window Server",
                  owner != "Dock",
                  owner != "loginwindow",
                  let layer = info[kCGWindowLayer as String] as? Int,
                  layer == 0,
                  let bounds = info[kCGWindowBounds as String] as? NSDictionary
            else { return false }
            let x = cgWindowDimension(bounds, "X")
            let y = cgWindowDimension(bounds, "Y")
            let width = cgWindowDimension(bounds, "Width")
            let height = cgWindowDimension(bounds, "Height")
            let windowFrame = NSRect(x: x, y: y, width: width, height: height)
            let widthMatches = abs(windowFrame.width - screenFrame.width) <= 12
            let heightMatches = windowFrame.height >= screenFrame.height * 0.88
            let horizontallyAligned = abs(windowFrame.midX - screenFrame.midX) <= 12
            return widthMatches && heightMatches && horizontallyAligned
        }
    }
    private func cgWindowDimension(_ bounds: NSDictionary, _ key: String) -> CGFloat {
        if let value = bounds[key] as? CGFloat {
            return value
        }
        if let value = bounds[key] as? NSNumber {
            return CGFloat(truncating: value)
        }
        return 0
    }
    private func installOverlayKeyMonitor() {
@@ -1119,6 +1163,11 @@
                return
            }
            if self.isAppOwnedDocumentWindow(keyWindow) {
                self.prepareSettingsWindow(keyWindow)
                return
            }
            // Settings/Preferences window -> float it above overlay for real-time preview.
            if self.isSettingsWindowCandidate(keyWindow) {
                self.prepareSettingsWindow(keyWindow)
@@ -1176,6 +1225,7 @@
    private func isSettingsWindowCandidate(_ window: NSWindow) -> Bool {
        if window == settingsWindow { return true }
        if window.identifier?.rawValue == "TagLauncherPreferencesWindow" { return true }
        if isAppOwnedDocumentWindow(window) { return true }
        guard NSApp.windows.contains(window),
              window != overlayWindow,
              window.isVisible,
@@ -1184,6 +1234,13 @@
        return settingsWindowTitleCandidates().contains(normalizedWindowTitle(window.title))
    }
    private func isAppOwnedDocumentWindow(_ window: NSWindow) -> Bool {
        NSApp.windows.contains(window)
            && window != overlayWindow
            && window.isVisible
            && !(window is NSPanel)
    }
    private func settingsWindowTitleCandidates() -> Set<String> {
        let keys = [
            "menu.preferences",
Release/AppStore-7.6.0-20260523.2335/APP_STORE_CONNECT_METADATA.md
Release/AppStore-7.6.0-20260523.2335/BUILD_SIGN_UPLOAD.md
File was renamed from Release/AppStore-7.6.0-20260523.1803/BUILD_SIGN_UPLOAD.md
@@ -50,7 +50,7 @@
APP_STORE=1 \
CODESIGN_IDENTITY="3rd Party Mac Developer Application: <TEAM NAME> (<TEAMID>)" \
APP_BUILD=20260523.1803 \
APP_BUILD=20260523.2335 \
bash build.sh
productbuild \
Release/AppStore-7.6.0-20260523.2335/FIRST_TIME_MAC_APP_STORE_TODO.md
Release/AppStore-7.6.0-20260523.2335/QA_RELEASE_EVIDENCE.md
File was renamed from Release/AppStore-7.6.0-20260523.1803/QA_RELEASE_EVIDENCE.md
@@ -3,7 +3,7 @@
## Local Build
- Version: `7.6.0`
- Build: `20260523.1803`
- Build: `20260523.2335`
- App path: `/Users/ar/Projects/Taglauncher/build/TagLauncher.app`
- DMG path: `/Users/ar/Projects/Taglauncher/build/TagLauncher.dmg`
- App executable size: `1.4M`
@@ -12,8 +12,8 @@
## Hashes
```text
c848f902a8219547b59767b172d5b26453748e7f34ee1ce7d10c421fc61005de  build/TagLauncher.dmg
8a541e00734f0bd5f461b13881e194b29cb9c33b79fc0203cd59864680928d4e  build/TagLauncher.app/Contents/MacOS/TagLauncher
370adb4ad671ec910cc0edf7e6f1ae3ebdd59cbd063f5d8439f2c7e8b05fd0e1  build/TagLauncher.dmg
9f808b6fc81b587a1508aa31c261cdbe187a3b7727daf8bf163788ba49eb2333  build/TagLauncher.app/Contents/MacOS/TagLauncher
```
## Commands Run
@@ -40,6 +40,8 @@
- Repeated self-launch keeps exactly one process.
- Repeated self-launch keeps exactly one Dock tile.
- Fullscreen Space overlay appears above the current fullscreen app.
- Fullscreen Space overlay is checked with `showDockIcon=true` and `showDockIcon=false`.
- Fullscreen Space overlay bounds must match the fullscreen target display and remain stable across repeated samples.
- App grid claims foreground, hides Dock, and keeps menu bar visible.
- Settings floats above app grid.
- File panel floats above Settings.
Release/AppStore-7.6.0-20260523.2335/README.md
File was renamed from Release/AppStore-7.6.0-20260523.1803/README.md
@@ -8,9 +8,9 @@
- Platform: macOS / Mac App Store
- Bundle ID: `com.taglauncher.app`
- Version: `7.6.0`
- Local QA build: `20260523.1803`
- Local QA build: `20260523.2335`
- Source branch at preparation time: `main`
- Release folder: `Release/AppStore-7.6.0-20260523.1803`
- Release folder: `Release/AppStore-7.6.0-20260523.2335`
## What Changed For This Release
@@ -21,6 +21,8 @@
- Removed the invalid all-spaces + move-to-active-space window behavior combination that caused the 07:23 crash.
- Removed the broken macOS system Help menu entry.
- Added an About-page Help PDF button that opens the online PDF matching the current app language, plus a copy-link button.
- Hardened fullscreen Space presentation so the app grid stays above the current fullscreen app instead of moving to another Space.
- Expanded window QA to cover fullscreen Space behavior with Dock icon both enabled and disabled, including stability checks.
- Strengthened the window logic QA script to cover fullscreen Space, duplicate Dock tiles, repeated self-launch, force quit layering, Settings/file panel layering, Quick Search, and two-display pointer-following behavior.
## Folder Contents
Scripts/window_logic_qa.sh
@@ -417,6 +417,17 @@
    guard let target = windows.first(where: { $0.name == "TagLauncherFullscreenQATargetFullscreen" }) else {
        fail("fullscreen target disappeared; TagLauncher likely switched to another Space")
    }
    let overlayWidth = dimension(tag[0].bounds, "Width")
    let overlayHeight = dimension(tag[0].bounds, "Height")
    let overlayMidX = dimension(tag[0].bounds, "X") + overlayWidth / 2
    let targetWidth = dimension(target.bounds, "Width")
    let targetHeight = dimension(target.bounds, "Height")
    let targetMidX = dimension(target.bounds, "X") + targetWidth / 2
    guard abs(overlayWidth - targetWidth) <= 12,
          overlayHeight >= targetHeight * 0.88,
          abs(overlayMidX - targetMidX) <= 12 else {
        fail("fullscreen overlay is not on the target fullscreen display: overlay=\(tag[0].bounds) target=\(target.bounds)")
    }
    guard tag[0].layer > target.layer else {
        fail("TagLauncher layer \(tag[0].layer) is not above fullscreen target layer \(target.layer)")
    }
@@ -562,6 +573,18 @@
  return 1
}
assert_fullscreen_overlay_stable() {
  local output=""
  for _ in {1..20}; do
    if ! output="$(swift "$assert_swift" fullscreen-overlay 2>&1)"; then
      printf '%s\n' "$output" >&2
      return 1
    fi
    sleep 0.1
  done
  printf '%s\n' "$output"
}
show_overlay() {
  send_main_hotkey
  if wait_swift_assert overlay >/dev/null 2>&1; then
@@ -650,16 +673,27 @@
sleep 0.4
wait_swift_assert no-overlay
log "==> QA fullscreen Space: overlay stays above the current fullscreen app"
run_fullscreen_space_case() {
  local dock_value="$1"
  log "==> QA fullscreen Space: overlay stays above the current fullscreen app (showDockIcon=$dock_value)"
  defaults write "$DEFAULTS_DOMAIN" showDockIcon -bool "$dock_value"
  prepare_isolated_app_instance
start_fullscreen_qa_target
send_main_hotkey
wait_swift_assert fullscreen-overlay
frontmost="$(osascript -e 'tell application "System Events" to get name of first application process whose frontmost is true')"
[[ "$frontmost" == "TagLauncher" ]] || { echo "FAIL: fullscreen overlay frontmost app is $frontmost, expected TagLauncher" >&2; exit 1; }
  assert_fullscreen_overlay_stable
send_keycode 53
sleep 0.4
wait_swift_assert no-overlay
kill_fullscreen_qa_target
}
run_fullscreen_space_case true
run_fullscreen_space_case false
log "==> Restoring dock-visible QA app instance for remaining checks"
defaults write "$DEFAULTS_DOMAIN" showDockIcon -bool true
prepare_isolated_app_instance
log "==> QA 1/7: overlay claims foreground, hides Dock, keeps menu bar visible"
show_overlay