From 26e2f4de52c4aef06d1b834fc101d7dfac3b810c Mon Sep 17 00:00:00 2001
From: Ariver <ar@MacBook-Air.local>
Date: Sat, 16 May 2026 02:05:32 +0800
Subject: [PATCH] Release TagLauncher 5.7.8 build 583

---
 Apptag/ApptagApp.swift            |  688 ++++++++++++++++++++++++-------
 Apptag/Localization/cs.json       |    2 
 Apptag/Localization/no.json       |    2 
 Apptag/Localization/zh-Hant.json  |    2 
 Apptag/Localization/ja.json       |    2 
 Apptag/Info.plist                 |    4 
 Apptag/Localization/th.json       |    2 
 prd-importantback.md              |   85 +++
 Apptag/Localization/nl.json       |    2 
 Apptag/Localization/sr-Cyrl.json  |    2 
 Apptag/Localization/zh-Hans.json  |    2 
 Apptag/Localization/de.json       |    2 
 Apptag/Localization/ms.json       |    2 
 Apptag/Localization/nn.json       |    2 
 Apptag/Localization/pt-BR.json    |    2 
 Apptag/Localization/es.json       |    2 
 AppStore_TODO_5.7.8_583.md        |   55 ++
 Apptag/Localization/tr.json       |    2 
 AppStore_Submission.md            |   23 
 Apptag/Localization/en.json       |    2 
 Apptag/Localization/nb.json       |    2 
 Apptag/Localization/ar.json       |    2 
 Apptag/Localization/vi.json       |    2 
 Apptag/Localization/fr.json       |    2 
 Apptag/Localization/id.json       |    2 
 Apptag/Localization/da.json       |    2 
 CHANGELOG.md                      |   86 +++
 Apptag/Localization/uk.json       |    2 
 Apptag/Localization/ar-Najdi.json |    2 
 Apptag/Localization/ko.json       |    2 
 Apptag/ContentView.swift          |   40 -
 Apptag/Localization/it.json       |    2 
 Apptag/Localization/sv.json       |    2 
 Apptag/Localization/ro.json       |    2 
 plan-deepthink.md                 |  262 +++++++++++
 Apptag/Localization/pl.json       |    2 
 Apptag/Localization/ru.json       |    2 
 37 files changed, 1,064 insertions(+), 237 deletions(-)

diff --git a/AppStore_Submission.md b/AppStore_Submission.md
index ff1bcc6..8681c7b 100644
--- a/AppStore_Submission.md
+++ b/AppStore_Submission.md
@@ -1,6 +1,6 @@
 # Apptag — Mac App Store 提审资料
 
-> 版本: 5.7.5 | Bundle ID: com.apptag.launcher | 更新日期: 2026-05-14
+> 版本: 5.7.8 | Bundle ID: com.apptag.launcher | 更新日期: 2026-05-15
 
 ---
 
@@ -11,8 +11,8 @@
 | **App 名称** | Apptag |
 | **副标题** | Tag-Based App Launcher |
 | **Bundle ID** | com.apptag.launcher |
-| **版本号** | 5.7.5 |
-| **Build 号** | 575 |
+| **版本号** | 5.7.8 |
+| **Build 号** | 583 |
 | **SKU** | apptag-mac-001 |
 | **主要类别** | Utilities (工具) |
 | **次要类别** | Productivity (效率) |
@@ -187,9 +187,20 @@
 ## 8. What's New (本次版本更新说明)
 
 ```
-Apptag 5.7.5
+Apptag 5.7.8
 
-- Fixed the menu bar icon visibility issue and added a Show Menu Bar Icon preference
+- Fixed duplicate overlay keyboard event monitors that could accumulate after repeatedly opening TagLauncher
+- Removed obsolete tag reorder fallback handlers that could interfere with click handling after drag sorting
+- Reduced unnecessary Dock refreshes when unrelated preferences change
+- Made the menu bar icon mandatory and removed the Show Menu Bar Icon preference across all localizations
+- Removed unused macOS app-menu items and added a localized Show App List command with symbol-only shortcut glyphs
+- Replaced the default macOS Services/Hide menu groups and made the menu bar status item use a fixed visible icon slot
+- Replaced the menu bar SF Symbol with a code-drawn template icon so the status item remains visible even if symbol rendering fails
+- Improved the General preferences layout for long localized text by using two-column style choices and aligned setting rows
+- Refined the app-list style chooser so Flat spans the first row and the remaining four styles form a balanced 2x2 grid
+- Reduced the app-list style option height and added compact visual glyphs for flat, masonry, and grid layouts
+- Added a symbol-only shortcut hint on the Language, Data, and About preference tabs for Option + Shift + Space
+- Fixed the menu bar icon visibility issue
 - Fixed Preferences placement when opened above TagLauncher from a fullscreen app Space
 - Fixed tag navigation reorder mode so clicking elsewhere exits the shaking state
 - Expanded localization support to 29 language variants
@@ -205,7 +216,7 @@
 - Added Mac App Store sandbox entitlements and App Store build validation
 - Hid Launch at Login controls in sandboxed App Store builds
 - Removed deprecated app launch API usage
-- Version updated to 5.7.5 (Build 575)
+- Version kept at 5.7.8, Build updated to 583
 ```
 
 ---
diff --git a/AppStore_TODO_5.7.8_583.md b/AppStore_TODO_5.7.8_583.md
new file mode 100644
index 0000000..1177bf2
--- /dev/null
+++ b/AppStore_TODO_5.7.8_583.md
@@ -0,0 +1,55 @@
+# TagLauncher 5.7.8 (Build 583) App Store 提交 To-do List
+
+
+
+> 这个本机 .app/.dmg 是本地构建产物。去 App Store 提交时,最好在那台登录开发者账号的电脑上,用 source/ 里的源码重新用正式证书构建、签名、上传。
+
+## A. 换电脑前确认
+
+- 确认要提交的版本号是 `5.7.8`,Build 是 `583`。
+- 带走本文件夹里的 `AppStore_Submission.md`、`CHANGELOG.md`、`TagLauncher.dmg`、`TagLauncher.app`。
+- 如果要在另一台电脑重新签名构建,也带走源码快照文件夹或整个项目目录。
+- 测试时先退出旧版 TagLauncher,再启动新版,避免菜单栏状态项仍来自旧进程。
+- 如果菜单栏图标看不到,先检查 Thaw / Bartender / Ice / Hidden Bar / Dozer 等菜单栏管理软件是否隐藏了 TagLauncher。
+
+## B. 开发者电脑准备
+
+- 登录正确的 Apple Developer 账号。
+- 确认 Xcode 已安装,并安装 Command Line Tools。
+- 在 Xcode 账号里确认有 Mac App Distribution / Apple Distribution 证书。
+- 确认 App Store Connect 里 Bundle ID 是 `com.apptag.launcher`。
+- 确认 App Store Connect 已创建或选择 Apptag 的 `5.7.8` 版本。
+
+## C. 正式构建与签名
+
+- 在开发者电脑上用开发者证书重新构建,不要直接提交本机 ad-hoc 签名包。
+- 使用 App Store sandbox entitlements:`Apptag/TagLauncher.entitlements`。
+- 构建完成后确认 About 页显示 `版本 5.7.8(构建 583)`。
+- 确认 `LSMinimumSystemVersion` 是 `15.0`。
+- 本地打开新构建的 App,快速检查:
+  - `⌥⇧空格` 能呼出应用列表。
+  - 设置窗口能在当前 TagLauncher 浮层上方居中显示。
+  - 设置页语言切换正常。
+  - 通用页排版在英文、法语、意大利语、中文下都不拥挤。
+  - 数据页导入/导出分类与布局正常。
+
+## D. App Store Connect 填写
+
+- 复制 `AppStore_Submission.md` 里的 App 描述、关键词、隐私说明和审核备注。
+- 隐私标签选择“不收集任何数据”。
+- 最低系统版本填写 macOS `15.0`。
+- What's New 使用 `AppStore_Submission.md` 第 8 节。
+- 如果上传截图,优先展示:
+  - 主应用列表视图。
+  - 设置页语言切换。
+  - 通用页布局设置。
+  - 标签编辑/拖动排序。
+  - 导入/导出分类与布局。
+
+## E. 上传与提交审核
+
+- 用 Xcode Organizer / Transporter 上传正式签名构建。
+- 上传后在 App Store Connect 选择 Build `583`。
+- 填完审核信息后提交审核。
+- 提交后保留本提交包,方便审核反馈时对照版本。
+
diff --git a/Apptag/ApptagApp.swift b/Apptag/ApptagApp.swift
index db83bfe..a04f8b8 100644
--- a/Apptag/ApptagApp.swift
+++ b/Apptag/ApptagApp.swift
@@ -13,6 +13,10 @@
             PreferencesView()
         }
         .defaultSize(width: 880, height: 460)
+        .commands {
+            CommandGroup(replacing: .systemServices) { }
+            CommandGroup(replacing: .appVisibility) { }
+        }
     }
 }
 
@@ -24,29 +28,37 @@
 }
 
 final class AppDelegate: NSObject, NSApplicationDelegate {
-    private static let showMenuBarIconKey = "showMenuBarIcon"
+    private static let showDockIconKey = "showDockIcon"
+    private static let showAppListMenuItemIdentifier = NSUserInterfaceItemIdentifier("TagLauncherShowAppListMenuItem")
+    private static let showAppListShortcutGlyphs = "⌥⇧␣"
 
     private var statusItem: NSStatusItem?
     private var overlayWindow: NSWindow?
+    private var overlayKeyMonitor: Any?
     private var settingsWindow: NSWindow?    // Track Settings window to keep it above overlay
     private var hotkeyRef: EventHotKeyRef?
     private var isInEditMode = false  // Suppress auto-dismiss during editing
+    private var isConfiguringApplicationMenu = false
+    private var lastShowDockIcon: Bool?
+
+    static func refreshChromeSettings() {
+        (NSApp.delegate as? AppDelegate)?.syncChromeSettings(force: true)
+    }
 
     func applicationDidFinishLaunching(_ notification: Notification) {
         L10n.setup()
-        setupDefaultPreferences()
         migrateDefaultGroupName()
         TagDatabase.seedDefaultTags()
-        let showDock = UserDefaults.standard.bool(forKey: "showDockIcon")
-        NSApp.setActivationPolicy(showDock ? .regular : .accessory)
-        setupMenuBar()
+        syncChromeSettings(force: true)
         registerHotkey()
         observeOtherWindows()
         observeSettingsClose()
         observeEditMode()
+        observeApplicationMenuChanges()
         observeChromeSettings()
         observeLanguageChanges()
         setupLaunchAtLogin()
+        configureApplicationMenuWhenAvailable()
     }
 
     /// Dock icon click → show overlay (same as menubar "Show TagLauncher")
@@ -73,21 +85,27 @@
         // User has set a custom name — keep it
     }
 
-    private func setupDefaultPreferences() {
-        if UserDefaults.standard.object(forKey: Self.showMenuBarIconKey) == nil {
-            UserDefaults.standard.set(true, forKey: Self.showMenuBarIconKey)
-        }
-    }
-
-    /// Observe Dock/menu-bar visibility changes so they take effect immediately.
+    /// Observe Dock visibility changes so they take effect immediately.
     private func observeChromeSettings() {
         NotificationCenter.default.addObserver(
             forName: UserDefaults.didChangeNotification,
             object: nil, queue: .main
         ) { [weak self] _ in
-            let show = UserDefaults.standard.bool(forKey: "showDockIcon")
-            NSApp.setActivationPolicy(show ? .regular : .accessory)
-            self?.setupMenuBar()
+            self?.syncChromeSettings()
+        }
+    }
+
+    private func syncChromeSettings(force: Bool = false) {
+        let showDock = UserDefaults.standard.bool(forKey: Self.showDockIconKey)
+        let dockChanged = lastShowDockIcon != showDock
+
+        if force || dockChanged {
+            NSApp.setActivationPolicy(showDock ? .regular : .accessory)
+            lastShowDockIcon = showDock
+        }
+
+        if force {
+            setupMenuBar()
         }
     }
 
@@ -98,6 +116,7 @@
             object: nil, queue: .main
         ) { [weak self] _ in
             self?.setupMenuBar()
+            self?.configureApplicationMenuWhenAvailable()
         }
     }
 
@@ -160,35 +179,28 @@
     // MARK: - Menu Bar
 
     private func setupMenuBar() {
-        guard UserDefaults.standard.object(forKey: Self.showMenuBarIconKey) as? Bool ?? true else {
-            removeMenuBarItem()
-            return
-        }
-
-        if statusItem == nil {
-            statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)
-        }
+        removeMenuBarItem()
+        statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.squareLength)
         guard let statusItem else { return }
+        statusItem.isVisible = true
 
         if let button = statusItem.button {
-            let image = makeMenuBarIcon()
-            button.image = image
+            button.image = makeMenuBarIcon()
             button.imageScaling = .scaleProportionallyDown
             button.imagePosition = .imageOnly
-            button.title = image == nil ? "T" : ""
             button.toolTip = "TagLauncher — Tag-based app launcher"
             button.action = #selector(toggleOverlay)
             button.target = self
         }
 
         let menu = NSMenu()
-        menu.addItem(
-            NSMenuItem(
-                title: "\(tr("menu.show"))  ⇧⌥Space",
-                action: #selector(toggleOverlay),
-                keyEquivalent: ""
-            )
+        let showItem = NSMenuItem(
+            title: showAppListMenuTitle,
+            action: #selector(toggleOverlay),
+            keyEquivalent: ""
         )
+        showItem.target = self
+        menu.addItem(showItem)
         menu.addItem(.separator())
 
         // Version display
@@ -236,31 +248,166 @@
         statusItem.menu = menu
     }
 
+    private func makeMenuBarIcon() -> NSImage {
+        let image = NSImage(size: NSSize(width: 18, height: 18))
+        image.lockFocus()
+        defer { image.unlockFocus() }
+
+        NSColor.black.setFill()
+
+        let tagBody = NSBezierPath()
+        tagBody.move(to: NSPoint(x: 5.0, y: 2.2))
+        tagBody.line(to: NSPoint(x: 15.6, y: 5.0))
+        tagBody.line(to: NSPoint(x: 12.8, y: 15.8))
+        tagBody.line(to: NSPoint(x: 2.2, y: 13.0))
+        tagBody.close()
+        tagBody.fill()
+
+        NSGraphicsContext.current?.compositingOperation = .clear
+        NSBezierPath(ovalIn: NSRect(x: 10.9, y: 11.2, width: 3.2, height: 3.2)).fill()
+        NSGraphicsContext.current?.compositingOperation = .sourceOver
+
+        image.isTemplate = true
+        image.accessibilityDescription = "TagLauncher"
+        return image
+    }
+
+    private var showAppListMenuTitle: String {
+        "\(tr("menu.showAppList"))  \(Self.showAppListShortcutGlyphs)"
+    }
+
+    private func observeApplicationMenuChanges() {
+        NotificationCenter.default.addObserver(
+            forName: NSApplication.didBecomeActiveNotification,
+            object: NSApp, queue: .main
+        ) { [weak self] _ in
+            self?.configureApplicationMenuWhenAvailable(retries: 4)
+        }
+
+        NotificationCenter.default.addObserver(
+            forName: NSMenu.didAddItemNotification,
+            object: nil, queue: .main
+        ) { [weak self] notification in
+            guard let self,
+                  !self.isConfiguringApplicationMenu,
+                  let menu = notification.object as? NSMenu,
+                  menu === NSApp.mainMenu?.items.first?.submenu
+            else { return }
+            self.configureApplicationMenuWhenAvailable(retries: 2)
+        }
+    }
+
+    private func configureApplicationMenuWhenAvailable(retries: Int = 20) {
+        DispatchQueue.main.asyncAfter(deadline: .now() + 0.05) { [weak self] in
+            guard let self else { return }
+            guard NSApp.mainMenu?.items.first?.submenu != nil else {
+                if retries > 0 {
+                    self.configureApplicationMenuWhenAvailable(retries: retries - 1)
+                }
+                return
+            }
+
+            self.configureApplicationMenu()
+            if retries > 0 && self.applicationMenuNeedsCleanup() {
+                self.configureApplicationMenuWhenAvailable(retries: retries - 1)
+            }
+        }
+    }
+
+    private func configureApplicationMenu() {
+        guard !isConfiguringApplicationMenu else { return }
+        guard let appMenu = NSApp.mainMenu?.items.first?.submenu else { return }
+
+        isConfiguringApplicationMenu = true
+        defer { isConfiguringApplicationMenu = false }
+
+        removeUnusedDefaultItems(from: appMenu)
+        upsertShowAppListItem(in: appMenu)
+        normalizeSeparators(in: appMenu)
+    }
+
+    private func applicationMenuNeedsCleanup() -> Bool {
+        guard let appMenu = NSApp.mainMenu?.items.first?.submenu else { return true }
+        let hasUnusedDefaultItems = appMenu.items.contains(where: isUnusedDefaultApplicationMenuItem)
+        let hasShowAppListItem = appMenu.items.contains {
+            $0.identifier == Self.showAppListMenuItemIdentifier
+        }
+        return hasUnusedDefaultItems || !hasShowAppListItem
+    }
+
+    private func removeUnusedDefaultItems(from menu: NSMenu) {
+        for item in menu.items.reversed() where isUnusedDefaultApplicationMenuItem(item) {
+            menu.removeItem(item)
+        }
+    }
+
+    private func isUnusedDefaultApplicationMenuItem(_ item: NSMenuItem) -> Bool {
+        if let servicesMenu = NSApp.servicesMenu, item.submenu === servicesMenu {
+            return true
+        }
+        return item.action == #selector(NSApplication.hide(_:))
+            || item.action == #selector(NSApplication.hideOtherApplications(_:))
+            || item.action == #selector(NSApplication.unhideAllApplications(_:))
+    }
+
+    private func upsertShowAppListItem(in menu: NSMenu) {
+        if let existingItem = menu.items.first(where: { $0.identifier == Self.showAppListMenuItemIdentifier }) {
+            configureShowAppListItem(existingItem)
+            return
+        }
+
+        let item = NSMenuItem()
+        item.identifier = Self.showAppListMenuItemIdentifier
+        configureShowAppListItem(item)
+
+        if let settingsIndex = menu.items.firstIndex(where: { isSettingsMenuItem($0) }) {
+            menu.insertItem(item, at: settingsIndex + 1)
+        } else if let firstSeparatorIndex = menu.items.firstIndex(where: { $0.isSeparatorItem }) {
+            menu.insertItem(item, at: firstSeparatorIndex)
+        } else {
+            menu.addItem(item)
+        }
+    }
+
+    private func configureShowAppListItem(_ item: NSMenuItem) {
+        item.title = showAppListMenuTitle
+        item.action = #selector(toggleOverlay)
+        item.target = self
+        item.keyEquivalent = ""
+        item.keyEquivalentModifierMask = []
+        item.isEnabled = true
+    }
+
+    private func isSettingsMenuItem(_ item: NSMenuItem) -> Bool {
+        item.action == Selector(("showSettingsWindow:"))
+            || item.action == #selector(openPreferences)
+            || item.title == tr("menu.preferences")
+    }
+
+    private func normalizeSeparators(in menu: NSMenu) {
+        var indexesToRemove: [Int] = []
+        var previousWasSeparator = false
+
+        for (index, item) in menu.items.enumerated() {
+            guard item.isSeparatorItem else {
+                previousWasSeparator = false
+                continue
+            }
+            if index == 0 || index == menu.items.count - 1 || previousWasSeparator {
+                indexesToRemove.append(index)
+            }
+            previousWasSeparator = true
+        }
+
+        for index in indexesToRemove.reversed() {
+            menu.removeItem(at: index)
+        }
+    }
+
     private func removeMenuBarItem() {
         guard let statusItem else { return }
         NSStatusBar.system.removeStatusItem(statusItem)
         self.statusItem = nil
-    }
-
-    private func makeMenuBarIcon() -> NSImage? {
-        let size = NSSize(width: 18, height: 18)
-        let image = NSImage(size: size)
-        image.lockFocus()
-        NSColor.black.setFill()
-        let tagPath = NSBezierPath()
-        tagPath.move(to: NSPoint(x: 2.5, y: 10.5))
-        tagPath.line(to: NSPoint(x: 8.5, y: 16.5))
-        tagPath.line(to: NSPoint(x: 15.5, y: 16.5))
-        tagPath.line(to: NSPoint(x: 15.5, y: 9.5))
-        tagPath.line(to: NSPoint(x: 8.5, y: 2.5))
-        tagPath.close()
-        tagPath.appendOval(in: NSRect(x: 10.6, y: 12.2, width: 2.6, height: 2.6))
-        tagPath.windingRule = .evenOdd
-        tagPath.fill()
-        image.unlockFocus()
-        image.isTemplate = true
-        image.accessibilityDescription = "TagLauncher"
-        return image
     }
 
     // MARK: - Overlay Window
@@ -285,17 +432,28 @@
         overlayWindow?.setFrame(screen.frame, display: true)
         overlayWindow?.level = NSWindow.Level(rawValue: Int(CGWindowLevelForKey(.maximumWindow)))
 
-        // Local key monitor: catch Escape while overlay is up
-        NSEvent.addLocalMonitorForEvents(matching: .keyDown) { [weak self] event in
+        installOverlayKeyMonitor()
+
+        overlayWindow?.makeKeyAndOrderFront(nil)
+        overlayWindow?.orderFrontRegardless()
+    }
+
+    private func installOverlayKeyMonitor() {
+        guard overlayKeyMonitor == nil else { return }
+        overlayKeyMonitor = NSEvent.addLocalMonitorForEvents(matching: .keyDown) { [weak self] event in
             if event.keyCode == 53 { // Escape
                 self?.hideOverlay()
                 return nil
             }
             return event
         }
+    }
 
-        overlayWindow?.makeKeyAndOrderFront(nil)
-        overlayWindow?.orderFrontRegardless()
+    private func removeOverlayKeyMonitor() {
+        if let monitor = overlayKeyMonitor {
+            NSEvent.removeMonitor(monitor)
+            overlayKeyMonitor = nil
+        }
     }
 
     private func makeOverlayWindow(on screen: NSScreen) -> NSWindow {
@@ -333,7 +491,11 @@
 
     private func hideOverlay(force: Bool = false) {
         guard force || !isInEditMode else { return }
+        if let settingsWindow, settingsWindow.parent == overlayWindow {
+            detachSettingsWindow(settingsWindow)
+        }
         overlayWindow?.orderOut(nil)
+        removeOverlayKeyMonitor()
         if force {
             overlayWindow = nil
         }
@@ -419,8 +581,8 @@
 
     /// Settings must always appear centered over the current overlay view and float above it.
     private func prepareSettingsWindow(_ window: NSWindow) {
-        window.identifier = NSUserInterfaceItemIdentifier("TagLauncherPreferencesWindow")
         let settingsSize = NSSize(width: 880, height: 460)
+        window.identifier = NSUserInterfaceItemIdentifier("TagLauncherPreferencesWindow")
         window.minSize = settingsSize
         window.maxSize = settingsSize
         if abs(window.frame.width - settingsSize.width) > 0.5 || abs(window.frame.height - settingsSize.height) > 0.5 {
@@ -432,15 +594,18 @@
 
         if let overlayWindow, overlayWindow.isVisible {
             center(window, over: overlayWindow.frame)
-            window.level = NSWindow.Level(rawValue: overlayWindow.level.rawValue + 1)
             attachSettingsWindow(window, to: overlayWindow)
+            window.level = overlayWindow.level
         } else if let screen = screenUnderMouse() {
             center(window, over: screen.visibleFrame)
             window.level = .floating
             detachSettingsWindow(window)
         }
 
-        window.collectionBehavior.formUnion([.canJoinAllSpaces, .fullScreenAuxiliary, .moveToActiveSpace])
+        var behavior = window.collectionBehavior
+        behavior.remove(.canJoinAllSpaces)
+        behavior.formUnion([.fullScreenAuxiliary, .moveToActiveSpace])
+        window.collectionBehavior = behavior
         window.makeKeyAndOrderFront(nil)
         window.orderFrontRegardless()
         settingsWindow = window
@@ -618,8 +783,9 @@
 struct PreferencesView: View {
     private let settingsWindowWidth: CGFloat = 880
     private let settingsContentWidth: CGFloat = 820
-    private let settingsLabelWidth: CGFloat = 160
-    private let widePickerWidth: CGFloat = 650
+    private let generalContentWidth: CGFloat = 720
+    private let generalLabelWidth: CGFloat = 190
+    private let generalControlWidth: CGFloat = 500
     private let compactPickerWidth: CGFloat = 320
 
     @AppStorage("tagFontSize") private var tagFontSize: Double = 18
@@ -628,7 +794,6 @@
     @AppStorage("defaultGroupName") private var defaultGroupName = "Other"
     @AppStorage("displayMode") private var displayMode = "flat"
     @AppStorage("hideAppNames") private var hideAppNames = false
-    @AppStorage("showMenuBarIcon") private var showMenuBarIcon = true
     @AppStorage("showDockIcon") private var showDockIcon = false
     @AppStorage("launchAtLogin") private var launchAtLogin = true
     @State private var selectedLanguage = L10n.currentCode
@@ -702,6 +867,52 @@
             GridItem(.flexible(minimum: 220), spacing: 18, alignment: .top),
         ]
     }
+    private var displayModeColumns: [GridItem] {
+        [
+            GridItem(.flexible(minimum: 220), spacing: 10, alignment: .top),
+            GridItem(.flexible(minimum: 220), spacing: 10, alignment: .top),
+        ]
+    }
+    private var displayModeOptions: [(id: String, title: String)] {
+        [
+            ("flat", tr("settings.flat")),
+            ("container", tr("settings.container")),
+            ("coloredContainer", tr("settings.coloredContainer")),
+            ("gridContainer", tr("settings.gridContainer")),
+            ("coloredGridContainer", tr("settings.coloredGridContainer")),
+        ]
+    }
+    private var containerDisplayModeOptions: [(id: String, title: String)] {
+        Array(displayModeOptions.dropFirst())
+    }
+
+    private func generalSettingRow<Control: View>(
+        _ label: String,
+        description: String,
+        @ViewBuilder control: () -> Control
+    ) -> some View {
+        HStack(alignment: .top, spacing: 16) {
+            Text(label)
+                .font(.system(size: 14, weight: .semibold))
+                .multilineTextAlignment(.trailing)
+                .lineLimit(2)
+                .minimumScaleFactor(0.86)
+                .frame(width: generalLabelWidth, alignment: .trailing)
+                .padding(.top, 5)
+
+            VStack(alignment: .leading, spacing: 6) {
+                control()
+                    .frame(width: generalControlWidth, alignment: .leading)
+                Text(description)
+                    .font(.caption)
+                    .foregroundStyle(.secondary)
+                    .multilineTextAlignment(.leading)
+                    .fixedSize(horizontal: false, vertical: true)
+                    .frame(width: generalControlWidth, alignment: .leading)
+            }
+        }
+        .frame(width: generalContentWidth, alignment: .leading)
+    }
 
     var body: some View {
         ZStack {
@@ -741,6 +952,12 @@
                         }
                         .padding(.vertical, 4)
                     }
+                    .frame(maxHeight: 250)
+
+                    Spacer(minLength: 0)
+                    ShortcutHintView()
+                        .frame(maxWidth: .infinity, alignment: .center)
+                        .padding(.bottom, 2)
                 }
                 .frame(maxWidth: settingsContentWidth, alignment: .leading)
                 .tabItem { Label(tr("settings.language"), systemImage: "globe") }
@@ -760,8 +977,10 @@
                                 }
                             }
                     }
-                    Toggle(tr("settings.showMenuBarIcon"), isOn: $showMenuBarIcon)
                     Toggle(tr("settings.showInDock"), isOn: $showDockIcon)
+                        .onChange(of: showDockIcon) { _, _ in
+                            AppDelegate.refreshChromeSettings()
+                        }
                     Toggle(tr("settings.hideAppNames"), isOn: $hideAppNames)
                 }
                 .frame(maxWidth: .infinity, alignment: .center)
@@ -770,78 +989,64 @@
                 Divider()
                     .padding(.bottom, 16)
 
-                // Two-column layout: each row label (right-aligned) + controls (left-aligned)
-                // All pickers and descriptions share the same left edge
-                VStack(spacing: 16) {
-                    HStack(alignment: .top, spacing: 16) {
-                        Text(tr("settings.appListStyle"))
-                            .frame(width: settingsLabelWidth, alignment: .trailing)
-                        VStack(alignment: .leading, spacing: 4) {
-                            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")
+                VStack(spacing: 18) {
+                    generalSettingRow(tr("settings.appListStyle"), description: tr("settings.flatDesc")) {
+                        VStack(spacing: 8) {
+                            DisplayModeOptionButton(
+                                mode: "flat",
+                                title: tr("settings.flat"),
+                                isSelected: displayMode == "flat"
+                            ) {
+                                displayMode = "flat"
                             }
-                            .pickerStyle(.segmented)
-                            .frame(width: widePickerWidth, alignment: .leading)
-                            Text(tr("settings.flatDesc"))
-                                .font(.caption).foregroundStyle(.secondary)
-                                .fixedSize(horizontal: false, vertical: true)
-                        }
-                    }
-                    HStack(alignment: .top, spacing: 16) {
-                        Text(tr("settings.tagPosition"))
-                            .frame(width: settingsLabelWidth, alignment: .trailing)
-                        VStack(alignment: .leading, spacing: 4) {
-                            Picker("", selection: $tagPosition) {
-                                Text(tr("settings.left")).tag("left")
-                                Text(tr("settings.right")).tag("right")
-                                Text(tr("settings.top")).tag("top")
-                            }
-                            .pickerStyle(.segmented)
-                            .frame(width: compactPickerWidth, alignment: .leading)
-                            Text(tr("settings.tagPosDesc"))
-                                .font(.caption).foregroundStyle(.secondary)
-                                .fixedSize(horizontal: false, vertical: true)
-                        }
-                    }
-                    HStack(alignment: .top, spacing: 16) {
-                        Text(tr("settings.tagFontSize"))
-                            .frame(width: settingsLabelWidth, alignment: .trailing)
-                        VStack(alignment: .leading, spacing: 4) {
-                            Picker("", selection: $tagFontSize) {
-                                ForEach([16.0, 18.0, 20.0, 22.0, 24.0, 26.0], id: \.self) { size in
-                                    Text("\(Int(size))").tag(size)
+
+                            LazyVGrid(columns: displayModeColumns, alignment: .leading, spacing: 8) {
+                                ForEach(containerDisplayModeOptions, id: \.id) { option in
+                                    DisplayModeOptionButton(
+                                        mode: option.id,
+                                        title: option.title,
+                                        isSelected: displayMode == option.id
+                                    ) {
+                                        displayMode = option.id
+                                    }
                                 }
                             }
-                            .pickerStyle(.segmented)
-                            .frame(width: compactPickerWidth, alignment: .leading)
-                            Text(tr("settings.tagFontDesc"))
-                                .font(.caption).foregroundStyle(.secondary)
-                                .fixedSize(horizontal: false, vertical: true)
                         }
+                        .frame(width: generalControlWidth, alignment: .leading)
                     }
-                    HStack(alignment: .top, spacing: 16) {
-                        Text(tr("settings.iconSize"))
-                            .frame(width: settingsLabelWidth, alignment: .trailing)
-                        VStack(alignment: .leading, spacing: 4) {
-                            Picker("", selection: $iconSize) {
-                                ForEach([40.0, 48.0, 56.0, 64.0, 72.0, 80.0], id: \.self) { size in
-                                    Text("\(Int(size))").tag(size)
-                                }
-                            }
-                            .pickerStyle(.segmented)
-                            .frame(width: compactPickerWidth, alignment: .leading)
-                            Text(tr("settings.iconSizeDesc"))
-                                .font(.caption).foregroundStyle(.secondary)
-                                .fixedSize(horizontal: false, vertical: true)
+
+                    generalSettingRow(tr("settings.tagPosition"), description: tr("settings.tagPosDesc")) {
+                        Picker("", selection: $tagPosition) {
+                            Text(tr("settings.left")).tag("left")
+                            Text(tr("settings.right")).tag("right")
+                            Text(tr("settings.top")).tag("top")
                         }
+                        .pickerStyle(.segmented)
+                        .frame(width: compactPickerWidth, alignment: .leading)
+                    }
+
+                    generalSettingRow(tr("settings.tagFontSize"), description: tr("settings.tagFontDesc")) {
+                        Picker("", selection: $tagFontSize) {
+                            ForEach([16.0, 18.0, 20.0, 22.0, 24.0, 26.0], id: \.self) { size in
+                                Text("\(Int(size))").tag(size)
+                            }
+                        }
+                        .pickerStyle(.segmented)
+                        .frame(width: compactPickerWidth, alignment: .leading)
+                    }
+
+                    generalSettingRow(tr("settings.iconSize"), description: tr("settings.iconSizeDesc")) {
+                        Picker("", selection: $iconSize) {
+                            ForEach([40.0, 48.0, 56.0, 64.0, 72.0, 80.0], id: \.self) { size in
+                                Text("\(Int(size))").tag(size)
+                            }
+                        }
+                        .pickerStyle(.segmented)
+                        .frame(width: compactPickerWidth, alignment: .leading)
                     }
                 }
             }
-            .frame(maxWidth: settingsContentWidth, alignment: .center)
+            .frame(maxWidth: generalContentWidth, alignment: .center)
             .padding()
             .tabItem { Label(tr("settings.general"), systemImage: "gearshape") }
 
@@ -858,8 +1063,12 @@
             .onAppear { scanApps() }
 
             // Tab 4: Data
-            Form {
-                Section {
+            VStack(spacing: 0) {
+                Spacer(minLength: 96)
+
+                VStack(alignment: .leading, spacing: 8) {
+                    Text(tr("settings.backup"))
+                        .font(.headline)
                     HStack(spacing: 12) {
                         Button(tr("settings.export")) { exportTags() }
                             .buttonStyle(.bordered)
@@ -869,46 +1078,55 @@
                     Text(tr("settings.backupDesc"))
                         .font(.caption)
                         .foregroundStyle(.secondary)
-                } header: {
-                    Text(tr("settings.backup"))
                 }
+                .frame(width: 420, alignment: .leading)
+
+                Spacer(minLength: 42)
+                ShortcutHintView()
+                    .frame(maxWidth: .infinity, alignment: .center)
+                Spacer(minLength: 18)
             }
             .tabItem { Label(tr("settings.data"), systemImage: "externaldrive.fill") }
             .padding()
 
             // Tab 5: About
-            Form {
-                Section {
-                    HStack {
-                        if let icon = NSImage(named: NSImage.applicationIconName) {
-                            Image(nsImage: icon)
-                                .resizable()
-                                .frame(width: 128, height: 128)
-                        }
-                        VStack(alignment: .leading, spacing: 4) {
-                            Text("TagLauncher")
-                                .font(.title2)
-                                .fontWeight(.semibold)
-                            Text(tr("app.description"))
-                                .font(.body)
-                                .foregroundStyle(.secondary)
-                            Text("\(tr("app.version")) \(appVersion) (\(tr("app.build")) \(buildVersion))")
-                                .font(.callout)
-                                .foregroundStyle(.tertiary)
+            VStack(spacing: 0) {
+                Spacer(minLength: 72)
 
-                            Divider()
-                                .padding(.vertical, 4)
-
-                            Text("万物之中,希望最美")
-                                .font(.caption)
-                                .foregroundStyle(.secondary)
-                            Text("永桔@2026-18602102518")
-                                .font(.caption)
-                                .foregroundStyle(.secondary)
-                        }
+                HStack {
+                    if let icon = NSImage(named: NSImage.applicationIconName) {
+                        Image(nsImage: icon)
+                            .resizable()
+                            .frame(width: 128, height: 128)
                     }
-                    .padding(.leading, 100)
+                    VStack(alignment: .leading, spacing: 4) {
+                        Text("TagLauncher")
+                            .font(.title2)
+                            .fontWeight(.semibold)
+                        Text(tr("app.description"))
+                            .font(.body)
+                            .foregroundStyle(.secondary)
+                        Text("\(tr("app.version")) \(appVersion) (\(tr("app.build")) \(buildVersion))")
+                            .font(.callout)
+                            .foregroundStyle(.tertiary)
+
+                        Divider()
+                            .padding(.vertical, 4)
+
+                        Text("万物之中,希望最美")
+                            .font(.caption)
+                            .foregroundStyle(.secondary)
+                        Text("永桔@2026-18602102518")
+                            .font(.caption)
+                            .foregroundStyle(.secondary)
+                    }
                 }
+                .frame(width: 560, alignment: .leading)
+
+                Spacer(minLength: 30)
+                ShortcutHintView()
+                    .frame(maxWidth: .infinity, alignment: .center)
+                Spacer(minLength: 18)
             }
             .tabItem { Label(tr("settings.about"), systemImage: "info.circle") }
             .padding()
@@ -947,3 +1165,153 @@
         }
     }
 }
+
+private struct DisplayModeOptionButton: View {
+    let mode: String
+    let title: String
+    let isSelected: Bool
+    let action: () -> Void
+
+    var body: some View {
+        Button(action: action) {
+            HStack(spacing: 7) {
+                DisplayModeGlyph(mode: mode, isSelected: isSelected)
+                    .frame(width: 24, height: 18)
+                Text(title)
+                    .font(.system(size: 13, weight: .semibold))
+                    .multilineTextAlignment(.center)
+                    .lineLimit(2)
+                    .minimumScaleFactor(0.82)
+                    .foregroundStyle(isSelected ? Color.white : Color.primary)
+                    .frame(maxWidth: .infinity)
+            }
+            .frame(maxWidth: .infinity, minHeight: 34)
+            .padding(.horizontal, 9)
+            .padding(.vertical, 2)
+            .background(
+                RoundedRectangle(cornerRadius: 7, style: .continuous)
+                    .fill(isSelected ? Color.accentColor : Color(nsColor: .controlBackgroundColor))
+            )
+            .overlay(
+                RoundedRectangle(cornerRadius: 7, style: .continuous)
+                    .stroke(isSelected ? Color.accentColor : Color.secondary.opacity(0.18), lineWidth: 1)
+            )
+            .contentShape(RoundedRectangle(cornerRadius: 7, style: .continuous))
+        }
+        .buttonStyle(.plain)
+        .accessibilityLabel(title)
+    }
+}
+
+private struct DisplayModeGlyph: View {
+    let mode: String
+    let isSelected: Bool
+
+    var body: some View {
+        switch mode {
+        case "flat":
+            HStack(spacing: 2) {
+                cell(index: 0, width: 5, height: 5, colored: false)
+                cell(index: 1, width: 5, height: 5, colored: false)
+                cell(index: 2, width: 5, height: 5, colored: false)
+            }
+        case "gridContainer", "coloredGridContainer":
+            let colored = mode == "coloredGridContainer"
+            VStack(spacing: 2) {
+                HStack(spacing: 2) {
+                    cell(index: 0, width: 7, height: 6, colored: colored)
+                    cell(index: 1, width: 7, height: 6, colored: colored)
+                }
+                HStack(spacing: 2) {
+                    cell(index: 2, width: 7, height: 6, colored: colored)
+                    cell(index: 3, width: 7, height: 6, colored: colored)
+                }
+            }
+        default:
+            let colored = mode == "coloredContainer"
+            HStack(alignment: .bottom, spacing: 2) {
+                cell(index: 0, width: 5, height: 8, colored: colored)
+                cell(index: 1, width: 5, height: 14, colored: colored)
+                cell(index: 2, width: 5, height: 10, colored: colored)
+            }
+        }
+    }
+
+    private func cell(index: Int, width: CGFloat, height: CGFloat, colored: Bool) -> some View {
+        let fill = fillColor(index: index, colored: colored)
+        let stroke = isSelected ? Color.white.opacity(0.95) : Color.secondary.opacity(0.35)
+        return RoundedRectangle(cornerRadius: 1.8, style: .continuous)
+            .fill(fill)
+            .overlay(
+                RoundedRectangle(cornerRadius: 1.8, style: .continuous)
+                    .stroke(stroke, lineWidth: 0.8)
+            )
+            .frame(width: width, height: height)
+    }
+
+    private func fillColor(index: Int, colored: Bool) -> Color {
+        if isSelected {
+            return Color.white.opacity(colored ? 0.9 : 0.18)
+        }
+        guard colored else {
+            return Color.secondary.opacity(0.12)
+        }
+        let palette: [Color] = [.green, .purple, .blue, .orange]
+        return palette[index % palette.count].opacity(0.78)
+    }
+}
+
+private struct ShortcutHintView: View {
+    var body: some View {
+        HStack(spacing: 12) {
+            ShortcutKeycap(symbol: "⌥")
+            Text("+")
+                .font(.system(size: 22, weight: .semibold))
+                .foregroundStyle(.tertiary)
+            ShortcutKeycap(symbol: "⇧")
+            Text("+")
+                .font(.system(size: 22, weight: .semibold))
+                .foregroundStyle(.tertiary)
+            SpacebarKeycap()
+        }
+        .accessibilityLabel("⌥ ⇧ Space")
+        .allowsHitTesting(false)
+    }
+}
+
+private struct ShortcutKeycap: View {
+    let symbol: String
+
+    var body: some View {
+        Text(symbol)
+            .font(.system(size: 36, weight: .medium))
+            .foregroundStyle(.secondary)
+            .frame(width: 74, height: 58)
+            .background(KeycapBackground())
+    }
+}
+
+private struct SpacebarKeycap: View {
+    var body: some View {
+        ZStack {
+            KeycapBackground()
+            Capsule()
+                .fill(Color.secondary.opacity(0.38))
+                .frame(width: 72, height: 3)
+                .offset(y: 13)
+        }
+        .frame(width: 190, height: 58)
+    }
+}
+
+private struct KeycapBackground: View {
+    var body: some View {
+        RoundedRectangle(cornerRadius: 10, style: .continuous)
+            .fill(Color(nsColor: .controlBackgroundColor))
+            .overlay(
+                RoundedRectangle(cornerRadius: 10, style: .continuous)
+                    .stroke(Color.primary.opacity(0.12), lineWidth: 1)
+            )
+            .shadow(color: .black.opacity(0.10), radius: 8, x: 0, y: 3)
+    }
+}
diff --git a/Apptag/ContentView.swift b/Apptag/ContentView.swift
index 111e5cc..cc4bdea 100644
--- a/Apptag/ContentView.swift
+++ b/Apptag/ContentView.swift
@@ -237,7 +237,6 @@
     @State private var tagReorderFrames: [String: CGRect] = [:]
     @State private var tagNavDragModeActive = false
     @State private var tagNavDragItem: String? = nil
-    @State private var tagNavDismissMonitor: Any? = nil
     @State private var tagNavReorderFrames: [String: CGRect] = [:]
     @State private var hoveredContainer: String? = nil  // colored container lift
     // Fixed interaction for "Colorless Container": hover fills persistently; click clears.
@@ -348,12 +347,6 @@
                 userInfo: ["active": active]
             )
         }
-        .onChange(of: tagNavDragModeActive) { _, active in
-            updateTagNavDismissMonitor(active: active)
-        }
-        .onDisappear {
-            removeTagNavDismissMonitor()
-        }
     }
 
     /// Set edit phase with synchronous notification BEFORE state change.
@@ -382,16 +375,6 @@
                 sideLayout
             } else {
                 topLayout
-            }
-
-            if tagNavDragModeActive && tagNavDragItem == nil {
-                Color.clear
-                    .contentShape(Rectangle())
-                    .ignoresSafeArea()
-                    .onTapGesture {
-                        endTagNavReorder()
-                    }
-                    .zIndex(1)
             }
 
             Button {
@@ -1271,8 +1254,6 @@
             .onChanged { value in
                 guard canReorderTag(tagName) else { return }
                 switch value {
-                case .first(true):
-                    beginTagNavReorder(tagName)
                 case .second(true, let drag?):
                     if tagNavDragItem == nil {
                         beginTagNavReorder(tagName)
@@ -1305,27 +1286,6 @@
         }
         tagNavDragModeActive = false
         tagNavDragItem = nil
-        removeTagNavDismissMonitor()
-    }
-
-    private func updateTagNavDismissMonitor(active: Bool) {
-        if active {
-            guard tagNavDismissMonitor == nil else { return }
-            tagNavDismissMonitor = NSEvent.addLocalMonitorForEvents(matching: [.leftMouseDown, .rightMouseDown]) { event in
-                guard tagNavDragModeActive, editPhase == .none else { return event }
-                endTagNavReorder()
-                return nil
-            }
-        } else {
-            removeTagNavDismissMonitor()
-        }
-    }
-
-    private func removeTagNavDismissMonitor() {
-        if let monitor = tagNavDismissMonitor {
-            NSEvent.removeMonitor(monitor)
-            tagNavDismissMonitor = nil
-        }
     }
 
     private func reorderTagNavItem(at location: CGPoint) {
diff --git a/Apptag/Info.plist b/Apptag/Info.plist
index 39a10c1..82ab965 100644
--- a/Apptag/Info.plist
+++ b/Apptag/Info.plist
@@ -19,9 +19,9 @@
 	<key>CFBundlePackageType</key>
 	<string>APPL</string>
 	<key>CFBundleShortVersionString</key>
-	<string>5.7.5</string>
+	<string>5.7.8</string>
 	<key>CFBundleVersion</key>
-	<string>575</string>
+	<string>583</string>
 	<key>LSMinimumSystemVersion</key>
 	<string>15.0</string>
 	<key>NSHighResolutionCapable</key>
diff --git a/Apptag/Localization/ar-Najdi.json b/Apptag/Localization/ar-Najdi.json
index 28f133f..3c16b47 100644
--- a/Apptag/Localization/ar-Najdi.json
+++ b/Apptag/Localization/ar-Najdi.json
@@ -1,5 +1,6 @@
 {
   "menu.show": "ورّ TagLauncher",
+  "menu.showAppList": "ورّ قائمة التطبيقات",
   "menu.preferences": "التفضيلات…",
   "menu.quit": "خروج",
   "menu.language": "اللغة",
@@ -13,7 +14,6 @@
   "settings.about": "عن التطبيق",
   "settings.launchAtLogin": "يشتغل مع الدخول",
   "settings.showInDock": "إظهار في Dock",
-  "settings.showMenuBarIcon": "إظهار أيقونة شريط القوائم",
   "settings.appListStyle": "شكل قائمة التطبيقات:",
   "settings.flat": "مسطح",
   "settings.container": "حاوية بدون لون",
diff --git a/Apptag/Localization/ar.json b/Apptag/Localization/ar.json
index 787e31a..f4f3770 100644
--- a/Apptag/Localization/ar.json
+++ b/Apptag/Localization/ar.json
@@ -1,5 +1,6 @@
 {
   "menu.show": "إظهار TagLauncher",
+  "menu.showAppList": "إظهار قائمة التطبيقات",
   "menu.preferences": "التفضيلات…",
   "menu.quit": "إنهاء",
   "menu.language": "اللغة",
@@ -13,7 +14,6 @@
   "settings.about": "حول",
   "settings.launchAtLogin": "التشغيل عند تسجيل الدخول",
   "settings.showInDock": "إظهار في Dock",
-  "settings.showMenuBarIcon": "إظهار أيقونة شريط القوائم",
   "settings.appListStyle": "نمط قائمة التطبيقات:",
   "settings.flat": "مسطح",
   "settings.container": "حاوية بلا لون",
diff --git a/Apptag/Localization/cs.json b/Apptag/Localization/cs.json
index 9e13852..3ed3e51 100644
--- a/Apptag/Localization/cs.json
+++ b/Apptag/Localization/cs.json
@@ -1,5 +1,6 @@
 {
   "menu.show": "Zobrazit TagLauncher",
+  "menu.showAppList": "Zobrazit seznam aplikací",
   "menu.preferences": "Předvolby…",
   "menu.quit": "Ukončit",
   "menu.language": "Jazyk",
@@ -13,7 +14,6 @@
   "settings.about": "O aplikaci",
   "settings.launchAtLogin": "Spustit při přihlášení",
   "settings.showInDock": "Zobrazit v Docku",
-  "settings.showMenuBarIcon": "Zobrazit ikonu v řádku nabídek",
   "settings.appListStyle": "Styl seznamu aplikací:",
   "settings.flat": "Plochý",
   "settings.container": "Bezbarvý kontejner",
diff --git a/Apptag/Localization/da.json b/Apptag/Localization/da.json
index 742c04f..85105b8 100644
--- a/Apptag/Localization/da.json
+++ b/Apptag/Localization/da.json
@@ -1,5 +1,6 @@
 {
   "menu.show": "Vis TagLauncher",
+  "menu.showAppList": "Vis appliste",
   "menu.preferences": "Indstillinger…",
   "menu.quit": "Slut",
   "menu.language": "Sprog",
@@ -13,7 +14,6 @@
   "settings.about": "Om",
   "settings.launchAtLogin": "Start ved login",
   "settings.showInDock": "Vis i Dock",
-  "settings.showMenuBarIcon": "Vis ikon i menulinjen",
   "settings.appListStyle": "Applistestil:",
   "settings.flat": "Flad",
   "settings.container": "Farveløs beholder",
diff --git a/Apptag/Localization/de.json b/Apptag/Localization/de.json
index 1648ee7..c873c77 100644
--- a/Apptag/Localization/de.json
+++ b/Apptag/Localization/de.json
@@ -1,5 +1,6 @@
 {
   "menu.show": "TagLauncher anzeigen",
+  "menu.showAppList": "App-Liste anzeigen",
   "menu.preferences": "Einstellungen…",
   "menu.quit": "Beenden",
   "menu.language": "Sprache",
@@ -13,7 +14,6 @@
   "settings.about": "Über",
   "settings.launchAtLogin": "Beim Anmelden starten",
   "settings.showInDock": "Im Dock anzeigen",
-  "settings.showMenuBarIcon": "Menüleistensymbol anzeigen",
   "settings.appListStyle": "App-Listenstil:",
   "settings.flat": "Flach",
   "settings.container": "Farbloser Container",
diff --git a/Apptag/Localization/en.json b/Apptag/Localization/en.json
index 12f774a..cf572bf 100644
--- a/Apptag/Localization/en.json
+++ b/Apptag/Localization/en.json
@@ -1,5 +1,6 @@
 {
   "menu.show": "Show TagLauncher",
+  "menu.showAppList": "Show App List",
   "menu.preferences": "Preferences…",
   "menu.quit": "Quit",
   "menu.language": "Language",
@@ -13,7 +14,6 @@
   "settings.about": "About",
   "settings.launchAtLogin": "Launch at login",
   "settings.showInDock": "Show in Dock",
-  "settings.showMenuBarIcon": "Show menu bar icon",
   "settings.appListStyle": "App list style:",
   "settings.flat": "Flat",
   "settings.container": "Colorless Container",
diff --git a/Apptag/Localization/es.json b/Apptag/Localization/es.json
index ed11db5..385f2d0 100644
--- a/Apptag/Localization/es.json
+++ b/Apptag/Localization/es.json
@@ -1,5 +1,6 @@
 {
   "menu.show": "Mostrar TagLauncher",
+  "menu.showAppList": "Mostrar lista de apps",
   "menu.preferences": "Preferencias…",
   "menu.quit": "Salir",
   "menu.language": "Idioma",
@@ -13,7 +14,6 @@
   "settings.about": "Acerca de",
   "settings.launchAtLogin": "Iniciar al iniciar sesión",
   "settings.showInDock": "Mostrar en el Dock",
-  "settings.showMenuBarIcon": "Mostrar icono en la barra de menús",
   "settings.appListStyle": "Estilo de lista de apps:",
   "settings.flat": "Plano",
   "settings.container": "Contenedor sin color",
diff --git a/Apptag/Localization/fr.json b/Apptag/Localization/fr.json
index 11e094e..cbe2e65 100644
--- a/Apptag/Localization/fr.json
+++ b/Apptag/Localization/fr.json
@@ -1,5 +1,6 @@
 {
   "menu.show": "Afficher TagLauncher",
+  "menu.showAppList": "Afficher la liste des apps",
   "menu.preferences": "Préférences…",
   "menu.quit": "Quitter",
   "menu.language": "Langue",
@@ -13,7 +14,6 @@
   "settings.about": "À propos",
   "settings.launchAtLogin": "Lancer à l'ouverture de session",
   "settings.showInDock": "Afficher dans le Dock",
-  "settings.showMenuBarIcon": "Afficher l’icône de la barre des menus",
   "settings.appListStyle": "Style de liste d'applications :",
   "settings.flat": "Liste",
   "settings.container": "Conteneur sans couleur",
diff --git a/Apptag/Localization/id.json b/Apptag/Localization/id.json
index 4529646..c5f5048 100644
--- a/Apptag/Localization/id.json
+++ b/Apptag/Localization/id.json
@@ -1,5 +1,6 @@
 {
   "menu.show": "Tampilkan TagLauncher",
+  "menu.showAppList": "Tampilkan daftar app",
   "menu.preferences": "Preferensi…",
   "menu.quit": "Keluar",
   "menu.language": "Bahasa",
@@ -13,7 +14,6 @@
   "settings.about": "Tentang",
   "settings.launchAtLogin": "Mulai saat masuk",
   "settings.showInDock": "Tampilkan di Dock",
-  "settings.showMenuBarIcon": "Tampilkan ikon bilah menu",
   "settings.appListStyle": "Gaya daftar app:",
   "settings.flat": "Datar",
   "settings.container": "Kontainer tanpa warna",
diff --git a/Apptag/Localization/it.json b/Apptag/Localization/it.json
index a73ed69..0f20df1 100644
--- a/Apptag/Localization/it.json
+++ b/Apptag/Localization/it.json
@@ -1,5 +1,6 @@
 {
   "menu.show": "Mostra TagLauncher",
+  "menu.showAppList": "Mostra elenco app",
   "menu.preferences": "Preferenze…",
   "menu.quit": "Esci",
   "menu.language": "Lingua",
@@ -13,7 +14,6 @@
   "settings.about": "Informazioni",
   "settings.launchAtLogin": "Avvia al login",
   "settings.showInDock": "Mostra nel Dock",
-  "settings.showMenuBarIcon": "Mostra icona nella barra dei menu",
   "settings.appListStyle": "Stile elenco app:",
   "settings.flat": "Elenco",
   "settings.container": "Contenitore senza colore",
diff --git a/Apptag/Localization/ja.json b/Apptag/Localization/ja.json
index 57b51c6..146eb1d 100644
--- a/Apptag/Localization/ja.json
+++ b/Apptag/Localization/ja.json
@@ -1,5 +1,6 @@
 {
   "menu.show": "TagLauncherを表示",
+  "menu.showAppList": "アプリ一覧を表示",
   "menu.preferences": "設定…",
   "menu.quit": "終了",
   "menu.language": "言語",
@@ -13,7 +14,6 @@
   "settings.about": "概要",
   "settings.launchAtLogin": "ログイン時に起動",
   "settings.showInDock": "Dockに表示",
-  "settings.showMenuBarIcon": "メニューバーアイコンを表示",
   "settings.appListStyle": "アプリ一覧スタイル:",
   "settings.flat": "フラット",
   "settings.container": "無色コンテナ",
diff --git a/Apptag/Localization/ko.json b/Apptag/Localization/ko.json
index 1a0a4ce..0159799 100644
--- a/Apptag/Localization/ko.json
+++ b/Apptag/Localization/ko.json
@@ -1,5 +1,6 @@
 {
   "menu.show": "TagLauncher 보기",
+  "menu.showAppList": "앱 목록 보기",
   "menu.preferences": "환경설정…",
   "menu.quit": "종료",
   "menu.language": "언어",
@@ -13,7 +14,6 @@
   "settings.about": "정보",
   "settings.launchAtLogin": "로그인 시 시작",
   "settings.showInDock": "Dock에 표시",
-  "settings.showMenuBarIcon": "메뉴 막대 아이콘 표시",
   "settings.appListStyle": "앱 목록 스타일:",
   "settings.flat": "플랫",
   "settings.container": "무색 컨테이너",
diff --git a/Apptag/Localization/ms.json b/Apptag/Localization/ms.json
index 42c45d4..d547d15 100644
--- a/Apptag/Localization/ms.json
+++ b/Apptag/Localization/ms.json
@@ -1,5 +1,6 @@
 {
   "menu.show": "Tunjukkan TagLauncher",
+  "menu.showAppList": "Tunjukkan senarai app",
   "menu.preferences": "Keutamaan…",
   "menu.quit": "Keluar",
   "menu.language": "Bahasa",
@@ -13,7 +14,6 @@
   "settings.about": "Tentang",
   "settings.launchAtLogin": "Mula semasa log masuk",
   "settings.showInDock": "Tunjukkan dalam Dock",
-  "settings.showMenuBarIcon": "Tunjukkan ikon bar menu",
   "settings.appListStyle": "Gaya senarai app:",
   "settings.flat": "Datar",
   "settings.container": "Kontainer tanpa warna",
diff --git a/Apptag/Localization/nb.json b/Apptag/Localization/nb.json
index 3d1956a..02c1e47 100644
--- a/Apptag/Localization/nb.json
+++ b/Apptag/Localization/nb.json
@@ -1,5 +1,6 @@
 {
   "menu.show": "Vis TagLauncher",
+  "menu.showAppList": "Vis appliste",
   "menu.preferences": "Innstillinger…",
   "menu.quit": "Avslutt",
   "menu.language": "Språk",
@@ -13,7 +14,6 @@
   "settings.about": "Om",
   "settings.launchAtLogin": "Start ved innlogging",
   "settings.showInDock": "Vis i Dock",
-  "settings.showMenuBarIcon": "Vis menylinjeikon",
   "settings.appListStyle": "App-listestil:",
   "settings.flat": "Flat",
   "settings.container": "Fargeløs beholder",
diff --git a/Apptag/Localization/nl.json b/Apptag/Localization/nl.json
index 410a346..2c1fcf8 100644
--- a/Apptag/Localization/nl.json
+++ b/Apptag/Localization/nl.json
@@ -1,5 +1,6 @@
 {
   "menu.show": "Toon TagLauncher",
+  "menu.showAppList": "Toon applijst",
   "menu.preferences": "Voorkeuren…",
   "menu.quit": "Stop",
   "menu.language": "Taal",
@@ -13,7 +14,6 @@
   "settings.about": "Over",
   "settings.launchAtLogin": "Start bij inloggen",
   "settings.showInDock": "Toon in Dock",
-  "settings.showMenuBarIcon": "Toon menubalkicoon",
   "settings.appListStyle": "App-lijststijl:",
   "settings.flat": "Plat",
   "settings.container": "Kleurloze container",
diff --git a/Apptag/Localization/nn.json b/Apptag/Localization/nn.json
index 7398a8c..155bf42 100644
--- a/Apptag/Localization/nn.json
+++ b/Apptag/Localization/nn.json
@@ -1,5 +1,6 @@
 {
   "menu.show": "Vis TagLauncher",
+  "menu.showAppList": "Vis appliste",
   "menu.preferences": "Innstillingar…",
   "menu.quit": "Avslutt",
   "menu.language": "Språk",
@@ -13,7 +14,6 @@
   "settings.about": "Om",
   "settings.launchAtLogin": "Start ved innlogging",
   "settings.showInDock": "Vis i Dock",
-  "settings.showMenuBarIcon": "Vis menylinjeikon",
   "settings.appListStyle": "App-listestil:",
   "settings.flat": "Flat",
   "settings.container": "Fargeløs beholder",
diff --git a/Apptag/Localization/no.json b/Apptag/Localization/no.json
index 3d1956a..02c1e47 100644
--- a/Apptag/Localization/no.json
+++ b/Apptag/Localization/no.json
@@ -1,5 +1,6 @@
 {
   "menu.show": "Vis TagLauncher",
+  "menu.showAppList": "Vis appliste",
   "menu.preferences": "Innstillinger…",
   "menu.quit": "Avslutt",
   "menu.language": "Språk",
@@ -13,7 +14,6 @@
   "settings.about": "Om",
   "settings.launchAtLogin": "Start ved innlogging",
   "settings.showInDock": "Vis i Dock",
-  "settings.showMenuBarIcon": "Vis menylinjeikon",
   "settings.appListStyle": "App-listestil:",
   "settings.flat": "Flat",
   "settings.container": "Fargeløs beholder",
diff --git a/Apptag/Localization/pl.json b/Apptag/Localization/pl.json
index c381df8..de7c8f6 100644
--- a/Apptag/Localization/pl.json
+++ b/Apptag/Localization/pl.json
@@ -1,5 +1,6 @@
 {
   "menu.show": "Pokaż TagLauncher",
+  "menu.showAppList": "Pokaż listę aplikacji",
   "menu.preferences": "Preferencje…",
   "menu.quit": "Zakończ",
   "menu.language": "Język",
@@ -13,7 +14,6 @@
   "settings.about": "O aplikacji",
   "settings.launchAtLogin": "Uruchamiaj przy logowaniu",
   "settings.showInDock": "Pokaż w Docku",
-  "settings.showMenuBarIcon": "Pokaż ikonę na pasku menu",
   "settings.appListStyle": "Styl listy aplikacji:",
   "settings.flat": "Płaski",
   "settings.container": "Bezbarwny kontener",
diff --git a/Apptag/Localization/pt-BR.json b/Apptag/Localization/pt-BR.json
index 24777a2..35f43e9 100644
--- a/Apptag/Localization/pt-BR.json
+++ b/Apptag/Localization/pt-BR.json
@@ -1,5 +1,6 @@
 {
   "menu.show": "Mostrar TagLauncher",
+  "menu.showAppList": "Mostrar lista de apps",
   "menu.preferences": "Preferências…",
   "menu.quit": "Sair",
   "menu.language": "Idioma",
@@ -13,7 +14,6 @@
   "settings.about": "Sobre",
   "settings.launchAtLogin": "Iniciar ao fazer login",
   "settings.showInDock": "Mostrar no Dock",
-  "settings.showMenuBarIcon": "Mostrar ícone na barra de menus",
   "settings.appListStyle": "Estilo da lista de apps:",
   "settings.flat": "Plano",
   "settings.container": "Contêiner sem cor",
diff --git a/Apptag/Localization/ro.json b/Apptag/Localization/ro.json
index 33a2930..6f2fa9f 100644
--- a/Apptag/Localization/ro.json
+++ b/Apptag/Localization/ro.json
@@ -1,5 +1,6 @@
 {
   "menu.show": "Afișează TagLauncher",
+  "menu.showAppList": "Afișează lista aplicațiilor",
   "menu.preferences": "Preferințe…",
   "menu.quit": "Ieșire",
   "menu.language": "Limbă",
@@ -13,7 +14,6 @@
   "settings.about": "Despre",
   "settings.launchAtLogin": "Pornește la autentificare",
   "settings.showInDock": "Afișează în Dock",
-  "settings.showMenuBarIcon": "Afișează pictograma în bara de meniu",
   "settings.appListStyle": "Stil listă aplicații:",
   "settings.flat": "Plat",
   "settings.container": "Container fără culoare",
diff --git a/Apptag/Localization/ru.json b/Apptag/Localization/ru.json
index 11b379c..71801d2 100644
--- a/Apptag/Localization/ru.json
+++ b/Apptag/Localization/ru.json
@@ -1,5 +1,6 @@
 {
   "menu.show": "Показать TagLauncher",
+  "menu.showAppList": "Показать список приложений",
   "menu.preferences": "Настройки…",
   "menu.quit": "Выйти",
   "menu.language": "Язык",
@@ -13,7 +14,6 @@
   "settings.about": "О программе",
   "settings.launchAtLogin": "Запускать при входе",
   "settings.showInDock": "Показывать в Dock",
-  "settings.showMenuBarIcon": "Показывать значок в строке меню",
   "settings.appListStyle": "Стиль списка приложений:",
   "settings.flat": "Плоский",
   "settings.container": "Бесцветный контейнер",
diff --git a/Apptag/Localization/sr-Cyrl.json b/Apptag/Localization/sr-Cyrl.json
index 73a3d80..b5a1238 100644
--- a/Apptag/Localization/sr-Cyrl.json
+++ b/Apptag/Localization/sr-Cyrl.json
@@ -1,5 +1,6 @@
 {
   "menu.show": "Прикажи TagLauncher",
+  "menu.showAppList": "Прикажи листу апликација",
   "menu.preferences": "Подешавања…",
   "menu.quit": "Изађи",
   "menu.language": "Језик",
@@ -13,7 +14,6 @@
   "settings.about": "О програму",
   "settings.launchAtLogin": "Покрени при пријављивању",
   "settings.showInDock": "Прикажи у Dock-у",
-  "settings.showMenuBarIcon": "Прикажи икону у траци менија",
   "settings.appListStyle": "Стил листе апликација:",
   "settings.flat": "Равно",
   "settings.container": "Безбојни контејнер",
diff --git a/Apptag/Localization/sv.json b/Apptag/Localization/sv.json
index b7ed5f3..13ab7f0 100644
--- a/Apptag/Localization/sv.json
+++ b/Apptag/Localization/sv.json
@@ -1,5 +1,6 @@
 {
   "menu.show": "Visa TagLauncher",
+  "menu.showAppList": "Visa applista",
   "menu.preferences": "Inställningar…",
   "menu.quit": "Avsluta",
   "menu.language": "Språk",
@@ -13,7 +14,6 @@
   "settings.about": "Om",
   "settings.launchAtLogin": "Starta vid inloggning",
   "settings.showInDock": "Visa i Dock",
-  "settings.showMenuBarIcon": "Visa ikon i menyraden",
   "settings.appListStyle": "Applistestil:",
   "settings.flat": "Platt",
   "settings.container": "Färglös behållare",
diff --git a/Apptag/Localization/th.json b/Apptag/Localization/th.json
index 8583d63..05274ad 100644
--- a/Apptag/Localization/th.json
+++ b/Apptag/Localization/th.json
@@ -1,5 +1,6 @@
 {
   "menu.show": "แสดง TagLauncher",
+  "menu.showAppList": "แสดงรายการแอป",
   "menu.preferences": "การตั้งค่า…",
   "menu.quit": "ออก",
   "menu.language": "ภาษา",
@@ -13,7 +14,6 @@
   "settings.about": "เกี่ยวกับ",
   "settings.launchAtLogin": "เปิดเมื่อเข้าสู่ระบบ",
   "settings.showInDock": "แสดงใน Dock",
-  "settings.showMenuBarIcon": "แสดงไอคอนในแถบเมนู",
   "settings.appListStyle": "รูปแบบรายการแอป:",
   "settings.flat": "แบน",
   "settings.container": "คอนเทนเนอร์ไม่มีสี",
diff --git a/Apptag/Localization/tr.json b/Apptag/Localization/tr.json
index 03f5bc9..0226153 100644
--- a/Apptag/Localization/tr.json
+++ b/Apptag/Localization/tr.json
@@ -1,5 +1,6 @@
 {
   "menu.show": "TagLauncher’ı Göster",
+  "menu.showAppList": "Uygulama listesini göster",
   "menu.preferences": "Tercihler…",
   "menu.quit": "Çık",
   "menu.language": "Dil",
@@ -13,7 +14,6 @@
   "settings.about": "Hakkında",
   "settings.launchAtLogin": "Girişte başlat",
   "settings.showInDock": "Dock’ta göster",
-  "settings.showMenuBarIcon": "Menü çubuğu simgesini göster",
   "settings.appListStyle": "Uygulama listesi stili:",
   "settings.flat": "Düz",
   "settings.container": "Renksiz Kapsayıcı",
diff --git a/Apptag/Localization/uk.json b/Apptag/Localization/uk.json
index 8b14a62..d11f823 100644
--- a/Apptag/Localization/uk.json
+++ b/Apptag/Localization/uk.json
@@ -1,5 +1,6 @@
 {
   "menu.show": "Показати TagLauncher",
+  "menu.showAppList": "Показати список програм",
   "menu.preferences": "Параметри…",
   "menu.quit": "Вийти",
   "menu.language": "Мова",
@@ -13,7 +14,6 @@
   "settings.about": "Про програму",
   "settings.launchAtLogin": "Запускати під час входу",
   "settings.showInDock": "Показувати в Dock",
-  "settings.showMenuBarIcon": "Показувати значок у рядку меню",
   "settings.appListStyle": "Стиль списку застосунків:",
   "settings.flat": "Плоский",
   "settings.container": "Безбарвний контейнер",
diff --git a/Apptag/Localization/vi.json b/Apptag/Localization/vi.json
index 7a33821..f4add4a 100644
--- a/Apptag/Localization/vi.json
+++ b/Apptag/Localization/vi.json
@@ -1,5 +1,6 @@
 {
   "menu.show": "Hiển thị TagLauncher",
+  "menu.showAppList": "Hiển thị danh sách ứng dụng",
   "menu.preferences": "Tùy chọn…",
   "menu.quit": "Thoát",
   "menu.language": "Ngôn ngữ",
@@ -13,7 +14,6 @@
   "settings.about": "Giới thiệu",
   "settings.launchAtLogin": "Khởi động khi đăng nhập",
   "settings.showInDock": "Hiển thị trong Dock",
-  "settings.showMenuBarIcon": "Hiển thị biểu tượng trên thanh menu",
   "settings.appListStyle": "Kiểu danh sách ứng dụng:",
   "settings.flat": "Phẳng",
   "settings.container": "Vùng chứa không màu",
diff --git a/Apptag/Localization/zh-Hans.json b/Apptag/Localization/zh-Hans.json
index ce635c1..9134421 100644
--- a/Apptag/Localization/zh-Hans.json
+++ b/Apptag/Localization/zh-Hans.json
@@ -1,5 +1,6 @@
 {
   "menu.show": "显示 TagLauncher",
+  "menu.showAppList": "显示应用列表",
   "menu.preferences": "偏好设置…",
   "menu.quit": "退出",
   "menu.language": "语言",
@@ -13,7 +14,6 @@
   "settings.about": "关于",
   "settings.launchAtLogin": "登录时启动",
   "settings.showInDock": "在 Dock 中显示",
-  "settings.showMenuBarIcon": "显示菜单栏图标",
   "settings.appListStyle": "应用列表样式:",
   "settings.flat": "平铺",
   "settings.container": "无色容器",
diff --git a/Apptag/Localization/zh-Hant.json b/Apptag/Localization/zh-Hant.json
index 1fd7413..3258d52 100644
--- a/Apptag/Localization/zh-Hant.json
+++ b/Apptag/Localization/zh-Hant.json
@@ -1,5 +1,6 @@
 {
   "menu.show": "顯示 TagLauncher",
+  "menu.showAppList": "顯示應用列表",
   "menu.preferences": "偏好設定…",
   "menu.quit": "退出",
   "menu.language": "語言",
@@ -13,7 +14,6 @@
   "settings.about": "關於",
   "settings.launchAtLogin": "登入時啟動",
   "settings.showInDock": "在 Dock 中顯示",
-  "settings.showMenuBarIcon": "顯示選單列圖示",
   "settings.appListStyle": "應用程式列表樣式:",
   "settings.flat": "平鋪",
   "settings.container": "無色容器",
diff --git a/CHANGELOG.md b/CHANGELOG.md
index abdc866..194c64b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,91 @@
 # Apptag Changelog
 
+## [5.7.8] — 2026-05-15
+
+- 修复 TagLauncher 浮层每次打开都会重复添加 Escape 键本地监听的问题,关闭浮层时会同步释放监听器,避免事件监听累积
+- 清理普通应用列表中标签拖动排序的旧兜底逻辑,移除可能吞掉下一次点击的鼠标监听器和不可达透明点击层
+- 菜单栏图标改为强制显示,移除设置页里的「显示菜单栏图标」开关,并从全部 29 个语言包同步删除该设置文案
+- 清理 macOS 应用主菜单中的 Services / Hide / Hide Others / Show All 默认项,新增「显示应用列表」菜单项
+- 顶部应用菜单和状态栏菜单的「显示应用列表」均使用全语言本地化文案,并用 `⌥⇧␣` 图形符号展示快捷键
+- 用 SwiftUI `CommandGroup` 从源头替换 macOS 默认 Services/Hide 菜单组,并增加延迟重试清理,避免系统菜单重建后恢复旧菜单项
+- 菜单栏状态项改为固定方形长度并强制 `isVisible`,降低图标被系统计算成不可见宽度的风险
+- 优化 Dock 显示设置刷新逻辑:只有 `showDockIcon` 变化时才更新系统 activation policy,避免无关设置变更触发全局刷新
+- 菜单栏图标改为代码自绘 template 图标,不再依赖 SF Symbol 加载,避免系统符号渲染失败导致图标不可见
+- 优化「通用」设置页排版:保留顶部三个开关横排,将应用列表样式改为两列选择块,并统一后续设置行的标签列、控件列和说明文字左边缘
+- 微调「应用列表样式」选择区:Flat 单独占满第一行,其余四个样式按两列两行排列,形成更稳定的视觉宽度关系
+- 收窄「应用列表样式」选项块高度,并为平铺、瀑布流容器、等高网格增加小图示,帮助用户快速区分布局类型
+- 在设置页的「语言」「数据」「关于」三个空白较多的页签增加 `⌥ + ⇧ + 空格键` 图形化快捷键提示,避免影响「通用」和「标签」页的主要设置/编辑区域
+- 版本号保持 `5.7.8`,Build 更新为 `583`
+- 同步更新 `AppStore_Submission.md` 的版本信息和 What's New
+
+## [5.7.5] — 2026-05-14
+
+- 新增「显示菜单栏图标」设置项,默认开启,并放在「在 Dock 中显示」之前
+- 修复菜单栏小图标不显示或被系统符号渲染不稳定的问题,改为自绘 template 状态栏图标
+- 修复全屏应用 Space 中打开设置时,设置窗口跑到其他窗口/屏幕的问题:设置窗口作为当前 TagLauncher overlay 的子窗口置顶显示
+- 优化设置窗口关闭后 overlay 的焦点恢复,减少设置窗口和主浮层之间的层级错乱
+- 为普通应用列表中的标签导航增加拖动排序退出兜底,尝试解决拖动后标签持续抖动的问题
+- 版本号更新为 `5.7.5`,Build 更新为 `575`
+
+## [5.7.0] — 2026-05-13
+
+- 扩展语言支持到 29 个语言版本,新增法语、德语、巴西葡萄牙语、泰语、越南语、阿拉伯语、土耳其语、印尼语、捷克语、丹麦语、荷兰语、挪威语、马来语、波兰语、罗马尼亚语、瑞典语等
+- 中文保持简体/繁体支持,不增加广东话版本
+- 设置页「语言」页签改为两列纵向排版,适配语言数量增加后的空间压力
+- 优化语言自动匹配逻辑,支持更多系统语言代码映射
+- 修复状态栏菜单项在语言切换后的刷新逻辑,避免重复创建状态栏图标
+- 固定设置窗口尺寸为 880×460,减少不同页签之间的高度和宽度跳动
+- 版本号更新为 `5.7.0`,Build 更新为 `570`
+
+## [5.6.0] — 2026-05-13
+
+- 修复编辑模式下左侧标签拖动排序失效的问题,改用自定义手势和几何位置检测实现实时排序
+- 标签拖动排序增加动态动画反馈,拖动时左侧标签顺序和右侧对应标签容器同步变化
+- 普通应用列表视图中的顶部/左侧/右侧标签导航支持长按拖动排序
+- 标签排序逻辑覆盖全部 5 种应用列表样式,保证标签导航顺序和应用分组布局一致
+- 修复导入/导出分类与布局功能,失败时显示明确错误提示
+- App Store 沙盒构建下隐藏 Launch at Login 控件,避免沙盒环境中不可用功能误导用户
+- 移除已弃用的应用启动 API,统一使用现代 `NSWorkspace.OpenConfiguration`
+- 新增 App Store 沙盒 entitlements,支持 App Store 构建验证
+- 版本号更新为 `5.6.0`,Build 更新为 `560`
+
+## [5.5.0] — 2026-05-11
+
+- 新增设置页第一个页签「语言」,原有「通用 / 标签 / 数据 / 关于」整体后移
+- 支持在设置页直接切换语言,菜单和设置面板实时同步刷新
+- 语言切换时增加 macOS 风格的全局刷新过渡图标,降低界面瞬间重建的突兀感
+- 设置窗口默认宽度扩展到 880pt,并在当前 TagLauncher 应用列表视图中央浮动置顶
+- 多屏使用时,设置窗口会优先居中显示在当前 TagLauncher overlay 所在屏幕
+- 数据页按钮文案由「导出 tag / 导入 tag」改为「导出分类与布局 / 导入分类与布局」,并同步所有已支持语言
+- 增加旧版非沙盒数据到 App Store 沙盒容器的数据迁移逻辑,降低更新后标签和排序丢失风险
+- 新增 App Store 提审资料文档,记录版本信息、隐私说明、截图建议和审核备注
+- 版本号更新为 `5.5.0`,Build 更新为 `550`
+
+## [5.2.0] — 2026-05-11
+
+- 重做 App 拖动预览实现,改为 CALayer / AppKit 预览层方式,提升拖动过程的稳定性和跟手感
+- 优化拖动预览在 overlay 和全屏 Space 中的显示层级,减少拖动图标被遮挡或丢失的情况
+- 保持拖动投放逻辑与分类容器绑定,继续支持移动/复制到目标标签
+- 版本号更新为 `5.2.0`,Build 更新为 `520`
+
+## [5.1.12] — 2026-05-11
+
+- 新增 `AppDragCoordinator`,集中管理 App 图标拖动预览、投放目标注册和拖放完成回调
+- 优化 App 图标网格项尺寸计算,减少隐藏/显示 App 名称时的布局抖动
+- 新增 `TagGroupView`,拆分标签分组容器视图逻辑,降低主视图复杂度
+- 优化标签数据库导入/导出和排序数据结构处理
+- 调整图标资源生成流程,删除旧 `AppIcon.iconset`,使用压缩后的 `icon-icns.icns` 降低包体体积
+- 补充应用描述相关本地化文案
+- 版本号更新为 `5.1.12`,Build 更新为 `512`
+
+## [4.0.7] — 2026-05-11
+
+- 调整应用列表布局和 App 启动逻辑,修复部分网格容器视图下点击 App 后启动/关闭 overlay 不稳定的问题
+- 更新 9 个既有语言的视图名称和说明文案
+- 完善 `build.sh`,强化构建流程中的 bundle 结构、资源复制、权限设置、strip 和签名步骤
+- 新增 `make_dmg.sh`,支持生成 DMG 发布包
+- 版本号更新为 `4.0.7`,Build 更新为 `407`
+
 ## [4.0.0] — 2026-05-10
 
 - 新增「无色网格容器」和「彩色网格容器」两种 App 列表视图样式
diff --git a/plan-deepthink.md b/plan-deepthink.md
new file mode 100644
index 0000000..2c59e6a
--- /dev/null
+++ b/plan-deepthink.md
@@ -0,0 +1,262 @@
+> 2026.0511.18:02已创建:plan-deepthink.md
+>
+> 加入了一个小章节:
+>
+> macOS Backward Compatibility
+>
+> 内容包括:
+>
+> - 是否可能兼容 macOS 10.15 的结论
+> - 当前主要阻碍
+> - 编译探测结果
+> - 推荐路线
+> - 未来迁移任务清单
+>
+> 后续关于未来版本、长期功能、架构想法、兼容性计划,都可以继续追加到这个文件里。当前这个文件还未提交进 git。
+
+# Apptag Future Development Notes
+
+This document is the shared parking lot for future-facing ideas, deeper product thoughts, and technical plans that should not interrupt the current release flow.
+
+When a new future idea comes up, add it here first so it can be revisited, compared, and prioritized later.
+
+## macOS Backward Compatibility
+
+### Question
+
+Can Apptag eventually support older macOS versions, especially macOS 10.15 Catalina?
+
+### Current Conclusion
+
+Supporting macOS 10.15 is theoretically possible, but it is not a simple deployment-target change. The current codebase is built around newer SwiftUI and AppKit APIs, so macOS 10.15 support would require a dedicated compatibility pass and likely a separate branch.
+
+For the near-term App Store release, keeping the current macOS 15.0 minimum is safer.
+
+### Main Compatibility Blockers
+
+- The build currently targets only Apple Silicon:
+  - `arm64-apple-macosx15.0`
+  - macOS 10.15 only runs on Intel Macs, so a 10.15-compatible release would need an `x86_64-apple-macosx10.15` build, ideally packaged as a universal app when also supporting Apple Silicon.
+- The app currently uses the SwiftUI app lifecycle:
+  - `@main struct TagLauncherApp: App`
+  - `Settings { PreferencesView() }`
+  - This lifecycle is macOS 11+, so macOS 10.15 would require returning to an AppKit lifecycle with `NSApplicationDelegate`, manually managed preferences windows, and `NSHostingView`.
+- The UI uses many SwiftUI APIs that are not available on macOS 10.15, including:
+  - `LazyVGrid`
+  - `LazyVStack`
+  - `@AppStorage`
+  - `Label`
+  - `ProgressView`
+  - Newer `.onChange` signatures
+  - `.foregroundStyle`
+  - `.ultraThinMaterial` / `.ultraThickMaterial`
+  - `.controlSize`
+  - `symbolRenderingMode`
+- Several system APIs would need fallbacks:
+  - `NSImage(systemSymbolName:)` is macOS 11+.
+  - `UniformTypeIdentifiers` / `UTType.plainText` should be replaced or wrapped for older systems.
+  - `NSOpenPanel.allowedContentTypes = [.json]` should fall back to `allowedFileTypes = ["json"]`.
+  - `NSScreen.safeAreaInsets` should be guarded or removed for 10.15, where notch handling is not relevant.
+
+### Compile Probe Result
+
+A direct compile probe with:
+
+```bash
+swiftc -o /private/tmp/TagLauncher-compat-check \
+  -framework AppKit \
+  -framework SwiftUI \
+  -framework Carbon \
+  -sdk "$(xcrun --sdk macosx --show-sdk-path)" \
+  -target arm64-apple-macosx10.15 \
+  Apptag/*.swift
+```
+
+failed immediately on macOS 14+ API usage:
+
+```text
+'onChange(of:initial:_:)' is only available in macOS 14.0 or newer
+```
+
+That is only the first visible blocker; more availability issues would appear after fixing it.
+
+### Recommended Path
+
+- Do not block the 5.5.0 App Store release on macOS 10.15 compatibility.
+- If broader compatibility becomes a product goal, create a dedicated branch such as `compat/macos-10.15`.
+- Consider an intermediate target first:
+  - macOS 13 or 14: likely lower effort.
+  - macOS 10.15: larger migration, because it requires lifecycle, layout, storage-binding, symbol, drag/drop, and file-panel compatibility work.
+
+### Possible Migration Tasks
+
+- Add universal build support:
+  - `x86_64-apple-macosx10.15`
+  - `arm64-apple-macosx11.0+`
+  - merge with `lipo` or migrate to an Xcode project / Swift Package build flow.
+- Replace SwiftUI app lifecycle with an AppKit-compatible entrypoint.
+- Wrap or replace newer SwiftUI APIs with compatibility components.
+- Replace SF Symbol dependencies with bundled vector or PNG assets for older systems.
+- Add availability guards for file panels, screen safe areas, app launching, and drag/drop type identifiers.
+- Maintain a compatibility test checklist on an actual macOS 10.15 Intel machine or VM.
+
+## AI-Assisted App Categorization
+
+### Product Goal
+
+Help users create a useful TagLauncher category layout with very low effort. The user should be able to describe the desired tag names in natural language, then receive a proposed tag/category layout for the apps installed on their Mac.
+
+### Version Plan
+
+#### TagLauncher 6.0.0: Local Category Creator
+
+Ship a local-only version first. This version does not use a server and does not perform intelligent app assignment.
+
+Goal:
+
+- let the user speak or type desired category/tag names
+- parse the input into short tag chips
+- create those tags locally
+- append them to `tagOrder`
+- leave app assignment to the existing manual/editing workflow
+
+Why this belongs in 6.0.0:
+
+- no backend dependency
+- no LLM cost
+- no upload/privacy review burden
+- validates the entry point, wording, parsing, and user workflow before adding AI
+- gives users immediate value even before intelligent classification exists
+
+Suggested interaction:
+
+1. User opens "Create Categories" from Preferences > Tags.
+2. User types or dictates a messy list such as "工作 设计 写作 娱乐 系统工具".
+3. The app parses it into chips.
+4. The user can rename/delete/reorder chips.
+5. The app creates missing tags locally and preserves existing tags.
+6. The app shows a short success state and points the user back to edit mode for app assignment.
+
+Implementation notes:
+
+- Use macOS dictation into a regular text field; no custom audio handling in 6.0.0.
+- Accept separators such as newline, comma, Chinese comma, semicolon, slash, bullet, and spaces for short CJK phrases.
+- Trim whitespace and punctuation.
+- Reject empty labels and duplicates.
+- Warn when labels are too long.
+- Do not delete or overwrite existing tags.
+- Assign colors by cycling through `TagColor.allIndices`.
+- Save through the existing `TagDatabase.save` path.
+
+#### Later Version: AI Categorization Draft
+
+After 6.0.0 proves the local category-creation flow, add the server-backed AI draft feature.
+
+### Recommended AI Version
+
+Do not make the first version fully automatic. Use a trust-building flow:
+
+1. User opens an "AI Categorize" entry from Preferences > Tags or the overlay edit mode.
+2. App shows a compact dialog with:
+   - a text input for category names or natural-language intent
+   - suggested starter chips such as Work, Design, Writing, Development, Study, Entertainment, Utilities
+   - a clear privacy note explaining that app names, paths, bundle identifiers, and the user's requested category names will be uploaded only after confirmation
+3. User confirms upload.
+4. Server returns a draft JSON plan.
+5. App displays a preview:
+   - new tags
+   - tag order
+   - each app's proposed tag assignment
+   - conflicts and uncertain apps
+6. User can apply, edit, or cancel.
+7. Applying the plan writes through `TagDatabase.save`, after creating a local backup snapshot so the change can be undone.
+
+### Client Data To Send
+
+Send only the minimum needed for classification:
+
+- app display name
+- bundle identifier
+- install path category, for example `/Applications`, `/System/Applications`, `~/Applications`
+- current tags, if any
+- user-provided desired category names or intent
+- current app language
+
+Avoid uploading icons, full user paths beyond the app path already needed for local mapping, unrelated files, or usage history.
+
+### Server Strategy
+
+The backend does not need a heavy agent. It can be a small stateless API:
+
+- `POST /v1/categorization-plans`
+- validates input
+- normalizes category names
+- calls one lightweight LLM with a strict JSON schema
+- validates the returned JSON
+- returns a draft plan with confidence and reasons
+
+Use a provider-adapter layer so the app is not locked to one model vendor. For the first version, the model should output only structured JSON; no tool use, no browsing, no remote code execution.
+
+### Cost Strategy
+
+This is a low-token, single-turn classification problem. A typical request can stay small by sending app names, bundle IDs, and 5-15 desired categories. The cost-effective path is:
+
+- start with a cheap mini/flash/lite model
+- use strict JSON schema output
+- retry once with a stronger model only if validation fails or confidence is low
+- cache common app classification hints server-side by bundle identifier
+- optionally pre-classify well-known bundle IDs locally before calling the model
+
+### Output Plan Shape
+
+The server should return a draft, not a final overwrite:
+
+```json
+{
+  "version": 1,
+  "tags": [
+    { "name": "Design", "color": 1 }
+  ],
+  "tagOrder": ["Design"],
+  "assignments": [
+    {
+      "path": "/Applications/Figma.app",
+      "appName": "Figma",
+      "tags": ["Design"],
+      "confidence": 0.95,
+      "reason": "Design/prototyping tool"
+    }
+  ],
+  "unassigned": [],
+  "warnings": []
+}
+```
+
+The local app converts this draft into the existing `TagDatabase.Store` format:
+
+- `tags`: tag name to color
+- `tagOrder`: ordered category list
+- `appTags`: app path to tag-name array
+
+### Interaction Notes
+
+- Best entry point: Preferences > Tags, plus a small "AI Categorize" button in overlay edit mode.
+- Keep the first dialog small and discoverable, not a big chatbot.
+- Let users paste messy text; parse it into chips before upload.
+- Limit category names to short labels and warn on overly long names.
+- Never silently replace the user's existing layout. Show a before/after preview.
+- Add a one-click undo after applying.
+
+### Privacy And App Store Notes
+
+- Explicit upload confirmation is required.
+- The privacy copy should say exactly what is uploaded.
+- Add a local-only fallback: if the user declines upload, the app can still create empty tags from the typed list.
+- Update App Store privacy answers if the server feature ships.
+
+### Future Enhancements
+
+- Built-in local rules for common apps by bundle identifier.
+- User style presets: by work type, by life area, by frequency, by project, by app category.
+- Voice input using macOS dictation into the same text field, so the app does not need to handle audio processing in v1.
+- A later on-device model experiment if privacy becomes a key selling point, but this should not block the server-based MVP.
diff --git a/prd-importantback.md b/prd-importantback.md
new file mode 100644
index 0000000..e03f5d5
--- /dev/null
+++ b/prd-importantback.md
@@ -0,0 +1,85 @@
+# Important Design Notes
+
+This document records important product/design implementation ideas that should be easy to recover later.
+
+## View 2: Colorless Container
+
+The colorless container view groups apps by tag into independent frosted-material cards.
+
+Layout logic:
+
+- Use a masonry/waterfall layout.
+- Calculate how many columns fit in the available overlay width.
+- Estimate each tag card's height from its app count, current icon size, and card width.
+- Place each next tag group into the currently shortest column.
+- Each card uses a transparent background over `.ultraThinMaterial`, with only a subtle border by default.
+- When the user hovers or clicks a tag/card, the corresponding card is filled with that tag's color at low opacity, creating a temporary focus state.
+- Tag navigation hover/click scrolls to the matching card and also activates the same color fill.
+
+Design intent:
+
+- Keep the whole view visually light and non-color-dominant.
+- Use color only as an interaction/focus signal.
+- Let groups with different app counts occupy natural heights, so dense groups do not force empty space in neighboring groups.
+
+## View 4: Colorless Grid
+
+The colorless grid view also groups apps by tag into frosted-material cards, but aligns cards into row-based grid tracks instead of waterfall columns.
+
+Layout logic:
+
+- Choose a preferred number of grid tracks per row based on available width:
+  - 3 tracks on wide screens
+  - 2 tracks on medium screens
+  - 1 track on narrow screens
+- For each row, test span patterns:
+  - 3-track row: `1+1+1`, `1+2`, `2+1`, or `3`
+  - 2-track row: `1+1` or `2`
+  - 1-track row: `1`
+- Pick the lowest-cost pattern by estimating how many icon rows each card needs.
+- Cards in the same row share the same fixed icon-row height, so their bottoms align.
+- Inside each card, apps are manually split into rows and empty cells are filled with clear placeholders to preserve grid alignment.
+- Like colorless container view, the card is transparent over `.ultraThinMaterial` by default and only receives low-opacity tag color on hover/click focus.
+
+Design intent:
+
+- Preserve the clarity of grouped cards while making rows visually orderly.
+- Avoid the uneven bottom edges of masonry when the user wants a stricter grid feeling.
+- Keep the "colorless by default, colored only on focus" interaction model consistent with View 2.
+
+## View 3: Colored Container
+
+The colored container view uses the same masonry/waterfall layout as View 2, but each tag card is always tinted with its tag color.
+
+Interaction logic:
+
+- The card background is always filled with the tag color at low opacity over `.ultraThinMaterial`.
+- Hovering a card raises visual emphasis with shadow and a slight scale-up.
+- Unlike the colorless container view, clicking the card does not toggle a persistent fill state because the card is already colored.
+- Tag navigation hover/click still scrolls to the matching card, but the key visual distinction comes from the card's permanent tag color.
+- App drag/drop behavior remains the same: dropping an app onto a card moves or copies it into that tag group.
+
+Design intent:
+
+- Make tag grouping immediately visible without requiring hover.
+- Use color as a persistent category marker.
+- Keep the masonry layout's compactness while making group identity stronger than View 2.
+
+## View 5: Colored Grid
+
+The colored grid view uses the same row-aligned grid layout as View 4, but each tag card is always tinted with its tag color.
+
+Interaction logic:
+
+- Cards are arranged with the View 4 grid-row algorithm: adaptive track count, span pattern selection, and equal fixed icon-row height per row.
+- Each card background is always filled with the tag color at low opacity over `.ultraThinMaterial`.
+- Hovering a card adds shadow and a slight scale-up to show focus.
+- Clicking does not toggle a fill state because the colored background is permanent.
+- Empty cells are still inserted inside each card to keep icon rows aligned.
+- App drag/drop behavior remains the same as other container views.
+
+Design intent:
+
+- Combine the stronger category recognition of colored cards with the orderly rhythm of the grid layout.
+- Make dense app libraries easier to scan by preserving row alignment.
+- Use hover only for focus emphasis, not for revealing color.

--
Gitblit v1.9.3