Ariver
2026-05-11 1a7643b2e7e8f9712533e5a68536ee3b23b0804b
Apptag/ApptagApp.swift
@@ -5,7 +5,7 @@
// MARK: - Application Entry Point
@main
struct ApptagApp: App {
struct TagLauncherApp: App {
    @NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
    var body: some Scene {
@@ -45,7 +45,7 @@
        setupLaunchAtLogin()
    }
    /// Dock icon click → show overlay (same as menubar "Show Apptag")
    /// Dock icon click → show overlay (same as menubar "Show TagLauncher")
    func applicationShouldHandleReopen(_ sender: NSApplication, hasVisibleWindows flag: Bool) -> Bool {
        showOverlay()
        return false  // Suppress default "unhide all windows" behavior
@@ -137,9 +137,9 @@
        if let button = statusItem.button {
            button.image = NSImage(
                systemSymbolName: "tag.fill",
                accessibilityDescription: "Apptag"
                accessibilityDescription: "TagLauncher"
            )
            button.toolTip = "Apptag — Tag-based app launcher"
            button.toolTip = "TagLauncher — Tag-based app launcher"
            button.action = #selector(toggleOverlay)
            button.target = self
        }
@@ -258,7 +258,7 @@
        panel.isReleasedWhenClosed = false
        panel.contentView = DismissibleHostingView(
            rootView: ContentView(hideOverlay: { [weak self] in
                self?.hideOverlay()
                self?.hideOverlay(force: true)
            }),
            onBackdropTap: { [weak self] in
                self?.hideOverlay()
@@ -267,9 +267,12 @@
        return panel
    }
    private func hideOverlay() {
        guard !isInEditMode else { return }
    private func hideOverlay(force: Bool = false) {
        guard force || !isInEditMode else { return }
        overlayWindow?.orderOut(nil)
        if force {
            overlayWindow = nil
        }
    }
    // MARK: - Global Hotkey (Shift+Option+Space)
@@ -294,7 +297,7 @@
        hotkeyRef = ref
        if status != noErr {
            print("[Apptag] Hotkey registration failed: \(status). Falling back to menu bar only.")
            print("[TagLauncher] Hotkey registration failed: \(status). Falling back to menu bar only.")
            return
        }
@@ -342,19 +345,26 @@
            // Settings/Preferences window → float it above overlay for real-time preview
            if NSApp.windows.contains(keyWindow) {
                if self.overlayWindow?.isVisible == true {
                    keyWindow.level = .popUpMenu
                }
                if self.settingsWindow == nil {
                    keyWindow.minSize = NSSize(width: 660, height: 380)
                    keyWindow.maxSize = NSSize(width: 660, height: CGFloat.greatestFiniteMagnitude)
                }
                self.settingsWindow = keyWindow
                self.prepareSettingsWindow(keyWindow)
                return
            }
            self.hideOverlay()
        }
    }
    /// Settings must always float above the overlay so changes can be previewed live.
    private func prepareSettingsWindow(_ window: NSWindow) {
        if overlayWindow?.isVisible == true {
            let overlayLevel = overlayWindow?.level.rawValue ?? NSWindow.Level.screenSaver.rawValue
            window.level = NSWindow.Level(rawValue: overlayLevel + 1)
            window.orderFrontRegardless()
        }
        if settingsWindow == nil {
            window.minSize = NSSize(width: 660, height: 380)
            window.maxSize = NSSize(width: 660, height: CGFloat.greatestFiniteMagnitude)
        }
        settingsWindow = window
    }
    /// Clean up settingsWindow reference when the Settings window closes.
@@ -375,7 +385,7 @@
    /// Track whether the overlay is in edit mode to suppress auto-dismiss.
    private func observeEditMode() {
        NotificationCenter.default.addObserver(
            forName: .apptagEditModeChanged,
            forName: .tagLauncherEditModeChanged,
            object: nil,
            queue: .main
        ) { [weak self] notification in
@@ -385,8 +395,13 @@
    @objc private func openPreferences() {
        // Don't hide overlay — keep it visible for real-time setting preview.
        // observeOtherWindows handles raising the Settings window above the overlay.
        NSApp.sendAction(Selector(("showSettingsWindow:")), to: nil, from: nil)
        DispatchQueue.main.async { [weak self] in
            guard let self else { return }
            for window in NSApp.windows where window != self.overlayWindow {
                self.prepareSettingsWindow(window)
            }
        }
    }
    @objc private func switchLanguage(_ sender: NSMenuItem) {
@@ -500,14 +515,14 @@
    private func exportTags() {
        let panel = NSSavePanel()
        panel.title = tr("settings.export")
        panel.nameFieldStringValue = "Apptag-tags.json"
        panel.nameFieldStringValue = "TagLauncher-tags.json"
        panel.allowedContentTypes = [.json]
        panel.begin { response in
            guard response == .OK, let url = panel.url else { return }
            do {
                try TagDatabase.exportTo(url)
            } catch {
                fputs("[Apptag] Export failed: \(error)\n", stderr)
                fputs("[TagLauncher] Export failed: \(error)\n", stderr)
            }
        }
    }
@@ -523,7 +538,7 @@
                _ = try TagDatabase.importFrom(url)
                scanApps()
            } catch {
                fputs("[Apptag] Import failed: \(error)\n", stderr)
                fputs("[TagLauncher] Import failed: \(error)\n", stderr)
                // Show alert on failure
                let alert = NSAlert()
                alert.messageText = tr("settings.importFailed")
@@ -574,9 +589,12 @@
                            Picker("", selection: $displayMode) {
                                Text(tr("settings.flat")).tag("flat")
                                Text(tr("settings.container")).tag("container")
                                Text(tr("settings.coloredContainer")).tag("coloredContainer")
                                Text(tr("settings.gridContainer")).tag("gridContainer")
                                Text(tr("settings.coloredGridContainer")).tag("coloredGridContainer")
                            }
                            .pickerStyle(.segmented)
                            .frame(width: 280, alignment: .leading)
                            .frame(width: 520, alignment: .leading)
                            Text(tr("settings.flatDesc"))
                                .font(.caption).foregroundStyle(.secondary)
                                .fixedSize(horizontal: false, vertical: true)
@@ -676,7 +694,7 @@
                                .frame(width: 128, height: 128)
                        }
                        VStack(alignment: .leading, spacing: 4) {
                            Text("Apptag")
                            Text("TagLauncher")
                                .font(.title2)
                                .fontWeight(.semibold)
                            Text(tr("app.description"))