Ariver
2026-05-24 ba3471291a3e343f335261d83aa9ab23fe5f699f
Keep secondary windows in fullscreen Space
2 files modified
5 files renamed
102 ■■■■ changed files
Apptag/ApptagApp.swift 39 ●●●● patch | view | raw | blame | history
Release/AppStore-7.6.0-20260524.1849/APP_STORE_CONNECT_METADATA.md patch | view | raw | blame | history
Release/AppStore-7.6.0-20260524.1849/BUILD_SIGN_UPLOAD.md 2 ●●● patch | view | raw | blame | history
Release/AppStore-7.6.0-20260524.1849/FIRST_TIME_MAC_APP_STORE_TODO.md patch | view | raw | blame | history
Release/AppStore-7.6.0-20260524.1849/QA_RELEASE_EVIDENCE.md 8 ●●●●● patch | view | raw | blame | history
Release/AppStore-7.6.0-20260524.1849/README.md 5 ●●●●● patch | view | raw | blame | history
Scripts/window_logic_qa.sh 48 ●●●●● patch | view | raw | blame | history
Apptag/ApptagApp.swift
@@ -67,6 +67,7 @@
    private var lastShowDockIcon: Bool?
    private var statusMenuScreenForNextOverlay: NSScreen?
    private var overlayGeneration = 0
    private var overlayAvoidsSpaceSwitch = false
    private var suppressReopenUntil = Date.distantPast
    private struct OverlayPlacementContext {
@@ -269,9 +270,8 @@
        let showDock = UserDefaults.standard.bool(forKey: Self.showDockIconKey)
        lastShowDockIcon = showDock
        let shouldStayAccessoryForCurrentFullscreenSpace = avoidSpaceSwitch
            && isOverlayVisible
            && !isSettingsVisible
        let shouldStayAccessoryForCurrentFullscreenSpace = isOverlayVisible
            && (avoidSpaceSwitch || overlayAvoidsSpaceSwitch)
        let desiredPolicy: NSApplication.ActivationPolicy = shouldStayAccessoryForCurrentFullscreenSpace
            ? .accessory
            : (requiresForegroundOwnership
@@ -784,6 +784,7 @@
        let placement = overlayPlacementContextForNextOverlay(preferredScreen: preferredScreen)
        let shouldAvoidSpaceSwitch = placement.map { hasFullscreenWindowOnScreen($0.screen) } ?? false
        if let overlayWindow, overlayWindow.isVisible {
            overlayAvoidsSpaceSwitch = shouldAvoidSpaceSwitch
            moveOverlayToCurrentPlacement(preferredScreen: preferredScreen)
            overlayWindow.level = currentOverlayLevel
            refreshLauncherChromeState(activate: !shouldAvoidSpaceSwitch, avoidSpaceSwitch: shouldAvoidSpaceSwitch)
@@ -839,6 +840,7 @@
            existingWindow.orderOut(nil)
            overlayWindow = nil
        }
        overlayAvoidsSpaceSwitch = shouldAvoidSpaceSwitch
        let window = makeOverlayWindow(
            on: placement.screen,
@@ -1098,6 +1100,7 @@
        overlayWindow?.orderOut(nil)
        removeOverlayKeyMonitor()
        removeQuickSearchExternalMouseMonitor()
        overlayAvoidsSpaceSwitch = false
        refreshLauncherChromeState()
        NotificationCenter.default.post(name: .tagLauncherOverlayDidHide, object: nil)
        if discardWindow {
@@ -1203,7 +1206,10 @@
    private func showQuickSearchFromGlobalHotkey() {
        if overlayWindow?.isVisible == true {
            refreshLauncherChromeState(activate: true)
            refreshLauncherChromeState(
                activate: !overlayAvoidsSpaceSwitch,
                avoidSpaceSwitch: overlayAvoidsSpaceSwitch
            )
            requestQuickSearch(source: QuickSearchOpenSource.globalVisible)
            return
        }
@@ -1299,12 +1305,20 @@
        var behavior = window.collectionBehavior
        behavior.remove(.canJoinAllSpaces)
        behavior.formUnion([.fullScreenAuxiliary, .moveToActiveSpace])
        if overlayAvoidsSpaceSwitch {
            behavior.remove(.moveToActiveSpace)
            behavior.formUnion([.fullScreenAuxiliary, .stationary, .transient, .ignoresCycle])
        } else {
            behavior.formUnion([.fullScreenAuxiliary, .moveToActiveSpace])
        }
        window.collectionBehavior = behavior
        window.makeKeyAndOrderFront(nil)
        window.orderFrontRegardless()
        settingsWindow = window
        refreshLauncherChromeState(activate: true)
        refreshLauncherChromeState(
            activate: !overlayAvoidsSpaceSwitch,
            avoidSpaceSwitch: overlayAvoidsSpaceSwitch
        )
    }
    private func attachSettingsWindow(_ window: NSWindow, to overlayWindow: NSWindow) {
@@ -1454,6 +1468,13 @@
    }
    private func promoteOverlayToForegroundInput() {
        if overlayAvoidsSpaceSwitch {
            refreshLauncherChromeState(activate: false, avoidSpaceSwitch: true)
            guard let overlayWindow else { return }
            overlayWindow.makeKeyAndOrderFront(nil)
            overlayWindow.orderFrontRegardless()
            return
        }
        beginLauncherForegroundOwnership()
        guard let overlayWindow else { return }
        overlayWindow.makeKeyAndOrderFront(nil)
@@ -1551,7 +1572,11 @@
    @objc private func openPreferences(_ sender: Any? = nil) {
        TagDatabase.flushPendingCategorySchemeBackupBatch()
        beginLauncherForegroundOwnership()
        if overlayAvoidsSpaceSwitch {
            refreshLauncherChromeState(activate: false, avoidSpaceSwitch: true)
        } else {
            beginLauncherForegroundOwnership()
        }
        // Don't hide overlay — keep it visible for real-time setting preview.
        if let overlayWindow, overlayWindow.isVisible {
            overlayWindow.makeKeyAndOrderFront(nil)
Release/AppStore-7.6.0-20260524.1849/APP_STORE_CONNECT_METADATA.md
Release/AppStore-7.6.0-20260524.1849/BUILD_SIGN_UPLOAD.md
File was renamed from Release/AppStore-7.6.0-20260524.1652/BUILD_SIGN_UPLOAD.md
@@ -50,7 +50,7 @@
APP_STORE=1 \
CODESIGN_IDENTITY="3rd Party Mac Developer Application: <TEAM NAME> (<TEAMID>)" \
APP_BUILD=20260524.1652 \
APP_BUILD=20260524.1849 \
bash build.sh
productbuild \
Release/AppStore-7.6.0-20260524.1849/FIRST_TIME_MAC_APP_STORE_TODO.md
Release/AppStore-7.6.0-20260524.1849/QA_RELEASE_EVIDENCE.md
File was renamed from Release/AppStore-7.6.0-20260524.1652/QA_RELEASE_EVIDENCE.md
@@ -3,7 +3,7 @@
## Local Build
- Version: `7.6.0`
- Build: `20260524.1652`
- Build: `20260524.1849`
- 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
1bf3e53410e18778038c310e1ed083cd89c27b781cc87942dd3dd9fb57d73cb2  build/TagLauncher.dmg
13e74a774662dfec88e69b5f51e5c51c5c46e3e5f8884cbf8cbab5cd44c4442d  build/TagLauncher.app/Contents/MacOS/TagLauncher
dc5bc9cf90b9e04a9e4fda155b757e6e0af681ff1b79bf109552ae0c3ce2a193  build/TagLauncher.dmg
a59f08f214bd4ac667197f6b2f2672aaf4a57c1fdd9872ea385099e84f18fa57  build/TagLauncher.app/Contents/MacOS/TagLauncher
```
## Commands Run
@@ -42,6 +42,8 @@
- 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.
- Fullscreen Space Quick Search opens from app grid without switching away from the current fullscreen app.
- Fullscreen Space Settings opens from app grid without switching away from the current fullscreen app.
- Split View fullscreen geometry is checked for 50/50 and 33/67 split layouts, while ordinary side-by-side desktop windows are rejected.
- App grid claims foreground, hides Dock, and keeps menu bar visible.
- Settings floats above app grid.
Release/AppStore-7.6.0-20260524.1849/README.md
File was renamed from Release/AppStore-7.6.0-20260524.1652/README.md
@@ -8,9 +8,9 @@
- Platform: macOS / Mac App Store
- Bundle ID: `com.taglauncher.app`
- Version: `7.6.0`
- Local QA build: `20260524.1652`
- Local QA build: `20260524.1849`
- Source branch at preparation time: `main`
- Release folder: `Release/AppStore-7.6.0-20260524.1652`
- Release folder: `Release/AppStore-7.6.0-20260524.1849`
## What Changed For This Release
@@ -23,6 +23,7 @@
- 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.
- Added Split View fullscreen detection so two apps sharing one fullscreen Space also keep the app grid on the current Space.
- Kept Quick Search and Settings inside the same fullscreen/Split View Space session instead of activating TagLauncher and sliding 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.
Scripts/window_logic_qa.sh
@@ -93,6 +93,26 @@
  osascript -e 'tell application "System Events" to keystroke "w" using {command down}'
}
close_settings_window() {
  osascript <<'OSA' >/dev/null 2>&1 || true
tell application "System Events"
  tell process "TagLauncher"
    repeat 20 times
      repeat with windowRef in windows
        try
          if (name of windowRef as text) is not "" then
            click button 1 of windowRef
            return
          end if
        end try
      end repeat
      delay 0.1
    end repeat
  end tell
end tell
OSA
}
dismiss_reopen_dialog() {
  osascript <<'OSA' >/dev/null 2>&1 || true
tell application "System Events"
@@ -438,6 +458,20 @@
        fail("TagLauncher layer \(tag[0].layer) is not above fullscreen target layer \(target.layer)")
    }
    print("PASS fullscreen overlay above target: overlayLayer=\(tag[0].layer) targetLayer=\(target.layer)")
case "fullscreen-settings":
    guard tag.count == 2 else { fail("fullscreen settings expected 2 TagLauncher windows, got \(tag.count)") }
    assertTagLayer(tag)
    guard let target = windows.first(where: { $0.name == "TagLauncherFullscreenQATargetFullscreen" }) else {
        fail("fullscreen target disappeared after opening settings; TagLauncher likely switched to another Space")
    }
    guard !tag[0].name.isEmpty, tag[1].name.isEmpty else {
        fail("fullscreen settings order wrong: \(tag.map(\.name))")
    }
    guard tag.allSatisfy({ $0.layer > target.layer }) else {
        fail("TagLauncher settings stack is not above fullscreen target")
    }
    print("PASS fullscreen settings above target: tagLayers=\(tag.map(\.layer)) targetLayer=\(target.layer)")
case "split-geometry":
    func isSingleFullscreenWindow(_ windowFrame: CGRect, on screenFrame: CGRect) -> Bool {
@@ -797,6 +831,20 @@
  send_main_hotkey
  wait_swift_assert fullscreen-overlay
  assert_fullscreen_overlay_stable
  log "==> QA fullscreen Space: quick search from appgrid does not switch Space"
  send_keycode 49
  sleep 0.4
  assert_fullscreen_overlay_stable
  send_keycode 53
  sleep 0.3
  wait_swift_assert fullscreen-overlay
  log "==> QA fullscreen Space: settings from appgrid does not switch Space"
  send_cmd_comma
  sleep 0.7
  wait_swift_assert fullscreen-settings
  close_settings_window
  sleep 0.5
  wait_swift_assert fullscreen-overlay
  send_keycode 53
  sleep 0.4
  wait_swift_assert no-overlay