From 9c317d89a39d0871f15f8282aec6c7f3dde7768c Mon Sep 17 00:00:00 2001
From: Ariver <shanghai3168@gmail.com>
Date: Fri, 26 Jun 2026 22:42:44 +0800
Subject: [PATCH] Polish Pro status UI and app grid layout
---
src/Apptag/Localization/fr.json | 2
src/Apptag/Localization/ru.json | 2
src/Apptag/AppGridCollectionView.swift | 24 +++
src/Apptag/Localization/ar.json | 2
src/Apptag/Localization/ro.json | 2
src/Apptag/Localization/en.json | 2
src/Apptag/Localization/sv.json | 2
src/Apptag/Localization/zh-Hant.json | 2
src/Apptag/ApptagApp.swift | 56 ++++++++
src/Apptag/Localization/pl.json | 2
src/Apptag/Localization/es.json | 2
src/Apptag/Localization/th.json | 2
src/Apptag/Localization/nn.json | 2
src/CHANGELOG.md | 10 +
src/Apptag/Info.plist | 4
src/Apptag/Localization/cs.json | 2
src/Apptag/Localization/ms.json | 2
src/Apptag/Localization/it.json | 2
src/Apptag/Localization/tr.json | 2
src/Apptag/QuickSearch.swift | 9 +
src/Apptag/Localization/da.json | 2
src/Apptag/Localization/id.json | 2
src/Scripts/window_logic_qa.sh | 74 ++++++++++
src/Apptag/Localization/ja.json | 2
src/Apptag/PreferencesView.swift | 109 ++++++++-------
src/Apptag/Localization/de.json | 2
src/Apptag/Localization/pt-BR.json | 2
CODEGRAPH.md | 10 +
src/Apptag/ProAccessViews.swift | 30 +++-
src/Apptag/Localization/ar-Najdi.json | 2
src/Apptag/Localization/nb.json | 2
src/Apptag/Localization/ko.json | 2
src/Apptag/Localization/no.json | 2
src/Apptag/ContentView.swift | 20 ++
src/Apptag/Localization/sr-Cyrl.json | 2
src/Apptag/Localization/zh-Hans.json | 2
src/Apptag/Localization/nl.json | 2
src/Apptag/Localization/uk.json | 2
src/Apptag/Localization/vi.json | 2
39 files changed, 338 insertions(+), 66 deletions(-)
diff --git a/CODEGRAPH.md b/CODEGRAPH.md
index df98068..d6b92c7 100644
--- a/CODEGRAPH.md
+++ b/CODEGRAPH.md
@@ -21,6 +21,7 @@
- `AppDelegate`:菜单栏状态项、Dock 显示策略、热键注册、窗口打开、Quick Search 触发、App 生命周期。
- 支持从标签导航双击进入设置页的 Tags tab。
- 启动时初始化 `ProEntitlementCenter`,保证免费 / Pro / 老用户自动 Pro 状态进入全局同步快照。
+ - 菜单栏下拉菜单包含免费 / Pro 当前身份状态项;免费状态点击进入 About 页的 Pro 卡片,Pro 状态使用金色皇冠图标。
- `src/Apptag/OverlayWindowController.swift`
- `OverlayPanel` / `OverlayWindowController`:AppGrid 浮层窗口与显示/隐藏控制。
- `src/Apptag/ContentView.swift`
@@ -29,9 +30,11 @@
- 编辑模式使用 runtime theme override:任何主题下进入编辑都临时显示默认浅色毛玻璃。
- 使用最近一次完整 `AppLibrarySnapshot` 先渲染 AppGrid,再后台刷新,避免启动/重开时立刻显示转圈。
- 使用技巧关闭提醒由 `ContentView` 统一弹 modal 并写入 `hideUsageTips` / `skipUsageTipsCloseReminder`。
+ - Quick Search 点击 TagLauncher 自身结果时只关闭当前浮层,不走外部自启动,避免 AppGrid 被重新拉起。
- `src/Apptag/PreferencesView.swift`
- `PreferencesView`:设置窗口、语言、通用、主题、快捷键、标签、数据、关于等设置页。
- Theme tab 只写视觉偏好 `appGridThemeID`,不得改变标签、排序、备注或分类数据。
+ - Pro 升级 / 权益卡位于 About tab;Data tab 只承载分类方案、导入导出和气泡显示位置等数据管理入口。
## AppGrid 显示与交互
@@ -42,6 +45,7 @@
- `AppGridIconNSView`:应用图标单元、点击、hover 气泡、拖拽起点。
- 底部使用技巧是原生 AppKit overlay,不允许改成 SwiftUI;必须拦截自身点击区域。
- 不拥有 AppGrid 全屏渐变背景,只接收 theme 推导出的玻璃和文本可读性 token。
+ - 样式 2 / 样式 3 的瀑布流容器布局会把 Mac 自带容器固定到底部并横向占满。
- `src/Apptag/AppGridTheme.swift`
- AppGrid 主题事实源:theme id、localization key、设置页 swatch、全屏渐变背景、玻璃明暗、编辑模式 token。
- 持久化 key:`appGridThemeID`。
@@ -51,7 +55,7 @@
- `ProEntitlementPolicy`:数据层可同步调用的 Pro 门禁;覆盖主题、导入 / 导出、备注额度和应用排序持久化。
- `ProEntitlementConfig`:一次性买断商品 ID、免费 + Pro 首个版本、免费主题集合、免费备注额度和 QA 环境变量的单一配置点。
- `src/Apptag/ProAccessViews.swift`
- - `ProStatusPill`:高级功能入口的小型 Pro 胶囊。
+ - `ProStatusPill`:高级功能入口的小型 Pro 胶囊;Pro / 已锁定高级功能使用金色皇冠体系,免费状态使用中性胶囊。
- `ProUpgradePromptView`:功能被锁时的统一解锁提示,购买 / 恢复动作由调用方闭包绑定。
- `src/Apptag/AppGridSupport.swift`
- 图标气泡、基础 metrics、气泡 placement 支撑。
@@ -85,6 +89,7 @@
- `LauncherHotkeyRegistrationStore`:快捷键注册状态持久化。
- `QuickSearchDocument` / `QuickSearchEngine`:搜索索引与排序。
- `QuickSearchPanelPresentationView` / `QuickSearchOverlayView` / `QuickSearchResultListHostView`:Quick Search 浮层 UI 与结果列表。
+ - `QuickSearchResultRowView`:结果行内部文字、图标、标签区域统一命中到整行,保证点击任意可见区域都能启动并关闭浮层。
## 智能分类与 Apple 默认应用资料
@@ -171,6 +176,7 @@
- 不得把主题、视觉偏好、窗口状态混入 `TagDatabase` 的分类/标签业务数据。
- 主题变化只允许影响 AppGrid 视觉,不得改变标签数据、排序、备注、SmartStart/category scheme、Quick Search、拖拽语义或导入导出。
- Pro 门禁不得写脏免费用户真实数据:主题预览不写 `appGridThemeID`,排序预览不写 `containerAppOrder`,第 6 个新备注不写 `appNotes` / `appNoteMetadata`,导入 / 导出拦截必须早于文件面板和文件读写。
+- Pro 升级 / 权益卡应放在 About tab;Data tab 不承载 Pro 营销卡,避免挤压数据管理布局。
- 免费主题固定为默认、深蓝、黑色;其它主题只能预览或在 Pro 解锁后持久化。
- `originalAppVersion` 早于免费 + Pro 首个版本的历史用户必须自动获得 Pro;`originalPurchaseDate` 只能作为版本不可判断时的兜底。
- 默认主题必须保留原浅色毛玻璃 AppGrid。
@@ -180,6 +186,8 @@
- 旧用户 `useDarkAppGrid=true` 必须迁移到 `deepBlue` 主题。
- AppGrid 启动或重开不应因为新 `ContentView` 初始 `allApps.isEmpty` 立刻显示转圈;应先复用最近一次完整快照,并延迟显示 loading。
- Quick Search 显示标题应优先使用解析后的 `AppInfo.displayName`,内部 bundle 名只作为搜索字段。
+- Quick Search 结果行内部文字 / 图标区域不得吞掉点击;点中行内任意可见区域都应按结果启动路径处理。
+- Quick Search-only 点击 TagLauncher 自身结果必须关闭浮层,不得重新打开 AppGrid。
- Apple 默认应用备注必须按当前语言显示,不允许 A 语言出现 B 语言备注。
- SmartStart/default system tag 初始化标签必须按当前语言显示;升级用户启动后应重刷带 `systemCategoryID` 的系统标签,不能长期保留旧英文初始化标签。
- 标签导航单击必须立即滚动;hover 必须保持受控自动滚动;长按排序必须保留;双击可以打开标签设置页但不得替代前三者。
diff --git a/src/Apptag/AppGridCollectionView.swift b/src/Apptag/AppGridCollectionView.swift
index d124a61..8e95bc8 100644
--- a/src/Apptag/AppGridCollectionView.swift
+++ b/src/Apptag/AppGridCollectionView.swift
@@ -1913,8 +1913,12 @@
let columnWidth = floor((availableWidth - gap * CGFloat(columnCount - 1)) / CGFloat(columnCount))
var columnHeights = Array(repeating: outerPadding, count: columnCount)
var items: [LayoutItem] = []
+ let bottomPinnedGroups = groups.enumerated().filter { $0.element.containerID == AppContainerID.appleBuiltIn }
+ let bottomPinnedIndexes = Set(bottomPinnedGroups.map { $0.offset })
for (index, group) in groups.enumerated() {
+ if bottomPinnedIndexes.contains(index) { continue }
+
let columnIndex = columnHeights.indices.min { columnHeights[$0] < columnHeights[$1] } ?? 0
let x = outerPadding + CGFloat(columnIndex) * (columnWidth + gap)
let y = columnHeights[columnIndex]
@@ -1932,8 +1936,24 @@
columnHeights[columnIndex] = y + height + gap
}
- let tallest = columnHeights.max() ?? outerPadding
- let contentHeight = (groups.isEmpty ? outerPadding * 2 : tallest - gap + outerPadding)
+ var contentBottom = columnHeights.max() ?? outerPadding
+ for (index, group) in bottomPinnedGroups {
+ let y = max(outerPadding, contentBottom)
+ let height = AppGridCollectionMetrics.cardHeight(
+ appCount: group.apps.count,
+ width: availableWidth,
+ iconSize: iconSize
+ )
+ items.append(
+ LayoutItem(
+ index: index,
+ frame: NSRect(x: outerPadding, y: y, width: availableWidth, height: height)
+ )
+ )
+ contentBottom = y + height + gap
+ }
+
+ let contentHeight = (items.isEmpty ? outerPadding * 2 : contentBottom - gap + outerPadding)
+ max(0, bottomContentPadding)
return LayoutPlan(
items: items,
diff --git a/src/Apptag/ApptagApp.swift b/src/Apptag/ApptagApp.swift
index c5b38a3..bb6b32b 100644
--- a/src/Apptag/ApptagApp.swift
+++ b/src/Apptag/ApptagApp.swift
@@ -34,6 +34,7 @@
private static let statusItemButtonIdentifier = NSUserInterfaceItemIdentifier("TagLauncherStatusItemButton")
private static let statusItemAccessibilityLabel = AppIdentity.displayName
private static let showAppListMenuItemIdentifier = NSUserInterfaceItemIdentifier("TagLauncherShowAppListMenuItem")
+ private static let proStatusMenuItemIdentifier = NSUserInterfaceItemIdentifier("TagLauncherProStatusMenuItem")
private static let helpMenuItemIdentifier = NSUserInterfaceItemIdentifier("TagLauncherHelpMenu")
private static let downloadHelpMenuItemIdentifier = NSUserInterfaceItemIdentifier("TagLauncherDownloadHelpMenuItem")
private static let externalActivationNotification = Notification.Name("TagLauncherExternalActivationRequested")
@@ -661,6 +662,9 @@
versionItem.isEnabled = false
menu.addItem(versionItem)
+ let proStatusItem = makeProStatusMenuItem()
+ menu.addItem(proStatusItem)
+
menu.addItem(.separator())
let prefsItem = NSMenuItem(
title: tr("menu.preferences"),
@@ -697,6 +701,7 @@
}
func menuWillOpen(_ menu: NSMenu) {
+ configureProStatusMenuItem(in: menu)
statusMenuScreenForNextOverlay = overlayController.screenContainingCurrentPointer()
?? statusItem?.button?.window?.screen
?? NSScreen.main
@@ -766,6 +771,53 @@
image.isTemplate = true
image.accessibilityDescription = "TagLauncher"
+ return image
+ }
+
+ private func makeProStatusMenuItem() -> NSMenuItem {
+ let item = NSMenuItem(
+ title: "",
+ action: #selector(openProStatusFromStatusMenu(_:)),
+ keyEquivalent: ""
+ )
+ item.identifier = Self.proStatusMenuItemIdentifier
+ item.target = self
+ configureProStatusMenuItem(item)
+ return item
+ }
+
+ private func configureProStatusMenuItem(in menu: NSMenu) {
+ guard let item = menu.items.first(where: { $0.identifier == Self.proStatusMenuItemIdentifier }) else {
+ return
+ }
+ configureProStatusMenuItem(item)
+ }
+
+ private func configureProStatusMenuItem(_ item: NSMenuItem) {
+ let isUnlocked = ProEntitlementPolicy.accessState().isUnlocked
+ item.title = tr(isUnlocked ? "menu.proStatus.unlocked" : "menu.proStatus.free")
+ item.action = #selector(openProStatusFromStatusMenu(_:))
+ item.target = self
+ item.isEnabled = true
+ item.image = isUnlocked ? makeProStatusMenuIcon() : nil
+ }
+
+ private func makeProStatusMenuIcon() -> NSImage? {
+ guard let symbol = NSImage(systemSymbolName: "crown.fill", accessibilityDescription: "Pro") else {
+ return nil
+ }
+ let configuration = NSImage.SymbolConfiguration(pointSize: 13, weight: .semibold)
+ let configuredSymbol = symbol.withSymbolConfiguration(configuration) ?? symbol
+ let image = NSImage(size: NSSize(width: 16, height: 16))
+ image.lockFocus()
+ NSColor(red: 0.96, green: 0.63, blue: 0.10, alpha: 1.0).set()
+ let iconRect = NSRect(x: 1.5, y: 1.5, width: 13, height: 13)
+ configuredSymbol.draw(in: iconRect, from: .zero, operation: .sourceOver, fraction: 1.0)
+ NSColor(red: 0.96, green: 0.63, blue: 0.10, alpha: 1.0).set()
+ iconRect.fill(using: .sourceAtop)
+ image.unlockFocus()
+ image.isTemplate = false
+ image.accessibilityDescription = "Pro"
return image
}
@@ -1700,6 +1752,10 @@
openPreferences(targetTab: nil)
}
+ @objc private func openProStatusFromStatusMenu(_ sender: NSMenuItem) {
+ openPreferences(targetTab: SettingsTabTarget.about)
+ }
+
private func openPreferences(targetTab: String?) {
dismissQuickSearchIfNeeded()
TagDatabase.flushPendingCategorySchemeBackupBatch()
diff --git a/src/Apptag/ContentView.swift b/src/Apptag/ContentView.swift
index 80fb99e..2560bb9 100644
--- a/src/Apptag/ContentView.swift
+++ b/src/Apptag/ContentView.swift
@@ -19,6 +19,7 @@
static let theme = "theme"
static let tags = "tags"
static let data = "data"
+ static let about = "about"
}
// MARK: - Edit Phase
@@ -2953,6 +2954,15 @@
closeQuickSearch(hideOverlayIfNeeded: true)
hideOverlay()
}
+ if isCurrentApp(app) {
+ if closeQuickSearchOnSuccess && !closeQuickSearchAndOverlayBeforeOpening {
+ closeQuickSearch(hideOverlayIfNeeded: closeOverlayOnSuccess)
+ }
+ if closeOverlayOnSuccess && !closeQuickSearchAndOverlayBeforeOpening {
+ hideOverlay()
+ }
+ return
+ }
let configuration = NSWorkspace.OpenConfiguration()
NSWorkspace.shared.openApplication(at: app.path, configuration: configuration) { _, error in
DispatchQueue.main.async {
@@ -2974,6 +2984,16 @@
}
}
+ private func isCurrentApp(_ app: AppInfo) -> Bool {
+ if app.bundleIdentifier?.caseInsensitiveCompare(AppIdentity.bundleIdentifier) == .orderedSame {
+ return true
+ }
+
+ let currentURL = Bundle.main.bundleURL.standardizedFileURL.resolvingSymlinksInPath()
+ let appURL = app.path.standardizedFileURL.resolvingSymlinksInPath()
+ return currentURL == appURL
+ }
+
func openApp(_ app: AppInfo) {
hideOverlay()
launchApp(app, closeOverlayOnSuccess: false)
diff --git a/src/Apptag/Info.plist b/src/Apptag/Info.plist
index 903dc0f..d2037c6 100644
--- a/src/Apptag/Info.plist
+++ b/src/Apptag/Info.plist
@@ -19,9 +19,9 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
- <string>8.1.0</string>
+ <string>8.1.1</string>
<key>CFBundleVersion</key>
- <string>20260626.1726</string>
+ <string>20260626.2229</string>
<key>LSApplicationCategoryType</key>
<string>public.app-category.utilities</string>
<key>LSMinimumSystemVersion</key>
diff --git a/src/Apptag/Localization/ar-Najdi.json b/src/Apptag/Localization/ar-Najdi.json
index 2e559b2..4bb424c 100644
--- a/src/Apptag/Localization/ar-Najdi.json
+++ b/src/Apptag/Localization/ar-Najdi.json
@@ -8,6 +8,8 @@
"menu.language": "اللغة",
"menu.help": "المساعدة",
"menu.version": "الإصدار",
+ "menu.proStatus.free": "أنت حالياً مستخدم مجاني (الترقية إلى Pro)",
+ "menu.proStatus.unlocked": "أنت حالياً مستخدم Pro مميز",
"settings.language": "اللغة",
"settings.languagePicker": "لغة التطبيق:",
"settings.languageDesc": "اختر لغة القوائم والإعدادات.",
diff --git a/src/Apptag/Localization/ar.json b/src/Apptag/Localization/ar.json
index 7f0410f..3bd3052 100644
--- a/src/Apptag/Localization/ar.json
+++ b/src/Apptag/Localization/ar.json
@@ -8,6 +8,8 @@
"menu.language": "اللغة",
"menu.help": "المساعدة",
"menu.version": "الإصدار",
+ "menu.proStatus.free": "أنت حالياً مستخدم مجاني (الترقية إلى Pro)",
+ "menu.proStatus.unlocked": "أنت حالياً مستخدم Pro مميز",
"settings.language": "اللغة",
"settings.languagePicker": "لغة التطبيق:",
"settings.languageDesc": "اختر اللغة المستخدمة في القوائم والإعدادات.",
diff --git a/src/Apptag/Localization/cs.json b/src/Apptag/Localization/cs.json
index 01e49be..9593aee 100644
--- a/src/Apptag/Localization/cs.json
+++ b/src/Apptag/Localization/cs.json
@@ -8,6 +8,8 @@
"menu.language": "Jazyk",
"menu.help": "Nápověda",
"menu.version": "Verze",
+ "menu.proStatus.free": "Nyní jste uživatel bezplatné verze (upgradovat na Pro)",
+ "menu.proStatus.unlocked": "Nyní jste vážený uživatel verze Pro",
"settings.language": "Jazyk",
"settings.languagePicker": "Jazyk aplikace:",
"settings.languageDesc": "Vyberte jazyk používaný v nabídkách a nastaveních.",
diff --git a/src/Apptag/Localization/da.json b/src/Apptag/Localization/da.json
index 68d9249..edd7934 100644
--- a/src/Apptag/Localization/da.json
+++ b/src/Apptag/Localization/da.json
@@ -8,6 +8,8 @@
"menu.language": "Sprog",
"menu.help": "Hjælp",
"menu.version": "Version",
+ "menu.proStatus.free": "Du er i øjeblikket gratisbruger (opgrader til Pro)",
+ "menu.proStatus.unlocked": "Du er i øjeblikket en værdsat Pro-bruger",
"settings.language": "Sprog",
"settings.languagePicker": "Appsprog:",
"settings.languageDesc": "Vælg sproget til menuer og indstillinger.",
diff --git a/src/Apptag/Localization/de.json b/src/Apptag/Localization/de.json
index 1bf72f8..5d04e9a 100644
--- a/src/Apptag/Localization/de.json
+++ b/src/Apptag/Localization/de.json
@@ -8,6 +8,8 @@
"menu.language": "Sprache",
"menu.help": "Hilfe",
"menu.version": "Version",
+ "menu.proStatus.free": "Sie sind derzeit Nutzer der Gratisversion (Upgrade auf Pro)",
+ "menu.proStatus.unlocked": "Sie sind derzeit ein geschätzter Pro-Nutzer",
"settings.language": "Sprache",
"settings.languagePicker": "App-Sprache:",
"settings.languageDesc": "Wähle die Sprache für Menüs und Einstellungen.",
diff --git a/src/Apptag/Localization/en.json b/src/Apptag/Localization/en.json
index e546bdf..bba7486 100644
--- a/src/Apptag/Localization/en.json
+++ b/src/Apptag/Localization/en.json
@@ -8,6 +8,8 @@
"menu.language": "Language",
"menu.help": "Help",
"menu.version": "Version",
+ "menu.proStatus.free": "You are currently a Free user (upgrade to Pro)",
+ "menu.proStatus.unlocked": "You are currently a valued Pro user",
"settings.language": "Language",
"settings.languagePicker": "App language:",
"settings.languageDesc": "Choose the language used by menus and settings.",
diff --git a/src/Apptag/Localization/es.json b/src/Apptag/Localization/es.json
index ae0f75f..9635fee 100644
--- a/src/Apptag/Localization/es.json
+++ b/src/Apptag/Localization/es.json
@@ -8,6 +8,8 @@
"menu.language": "Idioma",
"menu.help": "Ayuda",
"menu.version": "Versión",
+ "menu.proStatus.free": "Actualmente eres usuario gratuito (actualizar a Pro)",
+ "menu.proStatus.unlocked": "Actualmente eres un usuario Pro distinguido",
"settings.language": "Idioma",
"settings.languagePicker": "Idioma de la app:",
"settings.languageDesc": "Elige el idioma usado en los menús y ajustes.",
diff --git a/src/Apptag/Localization/fr.json b/src/Apptag/Localization/fr.json
index 7c74c73..de9a0a7 100644
--- a/src/Apptag/Localization/fr.json
+++ b/src/Apptag/Localization/fr.json
@@ -8,6 +8,8 @@
"menu.language": "Langue",
"menu.help": "Aide",
"menu.version": "Version",
+ "menu.proStatus.free": "Vous utilisez actuellement la version gratuite (passer à Pro)",
+ "menu.proStatus.unlocked": "Vous êtes actuellement un utilisateur Pro privilégié",
"settings.language": "Langue",
"settings.languagePicker": "Langue de l'app :",
"settings.languageDesc": "Choisissez la langue utilisée par les menus et les réglages.",
diff --git a/src/Apptag/Localization/id.json b/src/Apptag/Localization/id.json
index d8a8208..950bbc7 100644
--- a/src/Apptag/Localization/id.json
+++ b/src/Apptag/Localization/id.json
@@ -8,6 +8,8 @@
"menu.language": "Bahasa",
"menu.help": "Bantuan",
"menu.version": "Versi",
+ "menu.proStatus.free": "Saat ini Anda pengguna Gratis (tingkatkan ke Pro)",
+ "menu.proStatus.unlocked": "Saat ini Anda pengguna Pro istimewa",
"settings.language": "Bahasa",
"settings.languagePicker": "Bahasa aplikasi:",
"settings.languageDesc": "Pilih bahasa yang digunakan untuk menu dan pengaturan.",
diff --git a/src/Apptag/Localization/it.json b/src/Apptag/Localization/it.json
index 30583c6..cb27fae 100644
--- a/src/Apptag/Localization/it.json
+++ b/src/Apptag/Localization/it.json
@@ -8,6 +8,8 @@
"menu.language": "Lingua",
"menu.help": "Aiuto",
"menu.version": "Versione",
+ "menu.proStatus.free": "Attualmente sei un utente gratuito (passa a Pro)",
+ "menu.proStatus.unlocked": "Attualmente sei un utente Pro privilegiato",
"settings.language": "Lingua",
"settings.languagePicker": "Lingua app:",
"settings.languageDesc": "Scegli la lingua usata da menu e impostazioni.",
diff --git a/src/Apptag/Localization/ja.json b/src/Apptag/Localization/ja.json
index 258d99d..e52078f 100644
--- a/src/Apptag/Localization/ja.json
+++ b/src/Apptag/Localization/ja.json
@@ -8,6 +8,8 @@
"menu.language": "言語",
"menu.help": "ヘルプ",
"menu.version": "バージョン",
+ "menu.proStatus.free": "現在は無料版ユーザーです(Pro にアップグレード)",
+ "menu.proStatus.unlocked": "現在は特別な Pro ユーザーです",
"settings.language": "言語",
"settings.languagePicker": "アプリの言語:",
"settings.languageDesc": "メニューと設定で使う言語を選択します。",
diff --git a/src/Apptag/Localization/ko.json b/src/Apptag/Localization/ko.json
index f4dd987..5b92d99 100644
--- a/src/Apptag/Localization/ko.json
+++ b/src/Apptag/Localization/ko.json
@@ -8,6 +8,8 @@
"menu.language": "언어",
"menu.help": "도움말",
"menu.version": "버전",
+ "menu.proStatus.free": "현재 무료 사용자입니다(Pro로 업그레이드)",
+ "menu.proStatus.unlocked": "현재 특별 Pro 사용자입니다",
"settings.language": "언어",
"settings.languagePicker": "앱 언어:",
"settings.languageDesc": "메뉴와 설정에서 사용할 언어를 선택합니다.",
diff --git a/src/Apptag/Localization/ms.json b/src/Apptag/Localization/ms.json
index c15a6d2..4d95c02 100644
--- a/src/Apptag/Localization/ms.json
+++ b/src/Apptag/Localization/ms.json
@@ -8,6 +8,8 @@
"menu.language": "Bahasa",
"menu.help": "Bantuan",
"menu.version": "Versi",
+ "menu.proStatus.free": "Anda kini pengguna Percuma (naik taraf ke Pro)",
+ "menu.proStatus.unlocked": "Anda kini pengguna Pro istimewa",
"settings.language": "Bahasa",
"settings.languagePicker": "Bahasa aplikasi:",
"settings.languageDesc": "Pilih bahasa yang digunakan untuk menu dan tetapan.",
diff --git a/src/Apptag/Localization/nb.json b/src/Apptag/Localization/nb.json
index 69b4ca6..7276bf4 100644
--- a/src/Apptag/Localization/nb.json
+++ b/src/Apptag/Localization/nb.json
@@ -8,6 +8,8 @@
"menu.language": "Språk",
"menu.help": "Hjelp",
"menu.version": "Versjon",
+ "menu.proStatus.free": "Du er nå gratisbruker (oppgrader til Pro)",
+ "menu.proStatus.unlocked": "Du er nå en verdsatt Pro-bruker",
"settings.language": "Språk",
"settings.languagePicker": "Appspråk:",
"settings.languageDesc": "Velg språket som brukes i menyer og innstillinger.",
diff --git a/src/Apptag/Localization/nl.json b/src/Apptag/Localization/nl.json
index 07c4b32..3bfdbfa 100644
--- a/src/Apptag/Localization/nl.json
+++ b/src/Apptag/Localization/nl.json
@@ -8,6 +8,8 @@
"menu.language": "Taal",
"menu.help": "Help",
"menu.version": "Versie",
+ "menu.proStatus.free": "U bent momenteel een gratis gebruiker (upgrade naar Pro)",
+ "menu.proStatus.unlocked": "U bent momenteel een gewaardeerde Pro-gebruiker",
"settings.language": "Taal",
"settings.languagePicker": "App-taal:",
"settings.languageDesc": "Kies de taal voor menu’s en instellingen.",
diff --git a/src/Apptag/Localization/nn.json b/src/Apptag/Localization/nn.json
index 316807d..bb62912 100644
--- a/src/Apptag/Localization/nn.json
+++ b/src/Apptag/Localization/nn.json
@@ -8,6 +8,8 @@
"menu.language": "Språk",
"menu.help": "Hjelp",
"menu.version": "Versjon",
+ "menu.proStatus.free": "Du er no gratisbrukar (oppgrader til Pro)",
+ "menu.proStatus.unlocked": "Du er no ein verdsett Pro-brukar",
"settings.language": "Språk",
"settings.languagePicker": "Appspråk:",
"settings.languageDesc": "Velg språket som brukes i menyer og innstillinger.",
diff --git a/src/Apptag/Localization/no.json b/src/Apptag/Localization/no.json
index 69b4ca6..7276bf4 100644
--- a/src/Apptag/Localization/no.json
+++ b/src/Apptag/Localization/no.json
@@ -8,6 +8,8 @@
"menu.language": "Språk",
"menu.help": "Hjelp",
"menu.version": "Versjon",
+ "menu.proStatus.free": "Du er nå gratisbruker (oppgrader til Pro)",
+ "menu.proStatus.unlocked": "Du er nå en verdsatt Pro-bruker",
"settings.language": "Språk",
"settings.languagePicker": "Appspråk:",
"settings.languageDesc": "Velg språket som brukes i menyer og innstillinger.",
diff --git a/src/Apptag/Localization/pl.json b/src/Apptag/Localization/pl.json
index 0c7bb7d..544ce9e 100644
--- a/src/Apptag/Localization/pl.json
+++ b/src/Apptag/Localization/pl.json
@@ -8,6 +8,8 @@
"menu.language": "Język",
"menu.help": "Pomoc",
"menu.version": "Wersja",
+ "menu.proStatus.free": "Obecnie korzystasz z wersji bezpłatnej (uaktualnij do Pro)",
+ "menu.proStatus.unlocked": "Obecnie jesteś cenionym użytkownikiem Pro",
"settings.language": "Język",
"settings.languagePicker": "Język aplikacji:",
"settings.languageDesc": "Wybierz język menu i ustawień.",
diff --git a/src/Apptag/Localization/pt-BR.json b/src/Apptag/Localization/pt-BR.json
index d1b05eb..7b46e9e 100644
--- a/src/Apptag/Localization/pt-BR.json
+++ b/src/Apptag/Localization/pt-BR.json
@@ -8,6 +8,8 @@
"menu.language": "Idioma",
"menu.help": "Ajuda",
"menu.version": "Versão",
+ "menu.proStatus.free": "Você está na versão gratuita (atualizar para Pro)",
+ "menu.proStatus.unlocked": "Você é um usuário Pro especial",
"settings.language": "Idioma",
"settings.languagePicker": "Idioma do app:",
"settings.languageDesc": "Escolha o idioma usado nos menus e ajustes.",
diff --git a/src/Apptag/Localization/ro.json b/src/Apptag/Localization/ro.json
index c0f8c8e..8509fd6 100644
--- a/src/Apptag/Localization/ro.json
+++ b/src/Apptag/Localization/ro.json
@@ -8,6 +8,8 @@
"menu.language": "Limbă",
"menu.help": "Ajutor",
"menu.version": "Versiune",
+ "menu.proStatus.free": "În prezent sunteți utilizator gratuit (treceți la Pro)",
+ "menu.proStatus.unlocked": "În prezent sunteți un utilizator Pro apreciat",
"settings.language": "Limbă",
"settings.languagePicker": "Limba aplicației:",
"settings.languageDesc": "Alege limba folosită pentru meniuri și setări.",
diff --git a/src/Apptag/Localization/ru.json b/src/Apptag/Localization/ru.json
index ad88403..ddc4598 100644
--- a/src/Apptag/Localization/ru.json
+++ b/src/Apptag/Localization/ru.json
@@ -8,6 +8,8 @@
"menu.language": "Язык",
"menu.help": "Справка",
"menu.version": "Версия",
+ "menu.proStatus.free": "Сейчас вы пользователь бесплатной версии (перейти на Pro)",
+ "menu.proStatus.unlocked": "Сейчас вы ценный пользователь Pro",
"settings.language": "Язык",
"settings.languagePicker": "Язык приложения:",
"settings.languageDesc": "Выберите язык меню и настроек.",
diff --git a/src/Apptag/Localization/sr-Cyrl.json b/src/Apptag/Localization/sr-Cyrl.json
index 7fa91bd..ee023c3 100644
--- a/src/Apptag/Localization/sr-Cyrl.json
+++ b/src/Apptag/Localization/sr-Cyrl.json
@@ -8,6 +8,8 @@
"menu.language": "Језик",
"menu.help": "Помоћ",
"menu.version": "Верзија",
+ "menu.proStatus.free": "Тренутно сте корисник бесплатне верзије (надоградите на Pro)",
+ "menu.proStatus.unlocked": "Тренутно сте цењени Pro корисник",
"settings.language": "Језик",
"settings.languagePicker": "Језик апликације:",
"settings.languageDesc": "Изаберите језик менија и подешавања.",
diff --git a/src/Apptag/Localization/sv.json b/src/Apptag/Localization/sv.json
index 82af41b..ca1ea1d 100644
--- a/src/Apptag/Localization/sv.json
+++ b/src/Apptag/Localization/sv.json
@@ -8,6 +8,8 @@
"menu.language": "Språk",
"menu.help": "Hjälp",
"menu.version": "Version",
+ "menu.proStatus.free": "Du är för närvarande gratisanvändare (uppgradera till Pro)",
+ "menu.proStatus.unlocked": "Du är för närvarande en uppskattad Pro-användare",
"settings.language": "Språk",
"settings.languagePicker": "Appspråk:",
"settings.languageDesc": "Välj språket som används i menyer och inställningar.",
diff --git a/src/Apptag/Localization/th.json b/src/Apptag/Localization/th.json
index cb42deb..ef7e228 100644
--- a/src/Apptag/Localization/th.json
+++ b/src/Apptag/Localization/th.json
@@ -8,6 +8,8 @@
"menu.language": "ภาษา",
"menu.help": "ช่วยเหลือ",
"menu.version": "เวอร์ชัน",
+ "menu.proStatus.free": "ตอนนี้คุณเป็นผู้ใช้ฟรี (อัปเกรดเป็น Pro)",
+ "menu.proStatus.unlocked": "ตอนนี้คุณเป็นผู้ใช้ Pro ระดับพิเศษ",
"settings.language": "ภาษา",
"settings.languagePicker": "ภาษาของแอป:",
"settings.languageDesc": "เลือกภาษาที่ใช้ในเมนูและการตั้งค่า",
diff --git a/src/Apptag/Localization/tr.json b/src/Apptag/Localization/tr.json
index 49663a6..6dc43e8 100644
--- a/src/Apptag/Localization/tr.json
+++ b/src/Apptag/Localization/tr.json
@@ -8,6 +8,8 @@
"menu.language": "Dil",
"menu.help": "Yardım",
"menu.version": "Sürüm",
+ "menu.proStatus.free": "Şu anda ücretsiz kullanıcısınız (Pro’ya yükseltin)",
+ "menu.proStatus.unlocked": "Şu anda değerli bir Pro kullanıcısınız",
"settings.language": "Dil",
"settings.languagePicker": "Uygulama dili:",
"settings.languageDesc": "Menülerde ve ayarlarda kullanılan dili seçin.",
diff --git a/src/Apptag/Localization/uk.json b/src/Apptag/Localization/uk.json
index d2ac906..9169ccb 100644
--- a/src/Apptag/Localization/uk.json
+++ b/src/Apptag/Localization/uk.json
@@ -8,6 +8,8 @@
"menu.language": "Мова",
"menu.help": "Довідка",
"menu.version": "Версія",
+ "menu.proStatus.free": "Зараз ви користувач безкоштовної версії (оновити до Pro)",
+ "menu.proStatus.unlocked": "Зараз ви цінний користувач Pro",
"settings.language": "Мова",
"settings.languagePicker": "Мова застосунку:",
"settings.languageDesc": "Оберіть мову меню та налаштувань.",
diff --git a/src/Apptag/Localization/vi.json b/src/Apptag/Localization/vi.json
index bb046b0..37a643c 100644
--- a/src/Apptag/Localization/vi.json
+++ b/src/Apptag/Localization/vi.json
@@ -8,6 +8,8 @@
"menu.language": "Ngôn ngữ",
"menu.help": "Trợ giúp",
"menu.version": "Phiên bản",
+ "menu.proStatus.free": "Bạn hiện là người dùng Miễn phí (nâng cấp lên Pro)",
+ "menu.proStatus.unlocked": "Bạn hiện là người dùng Pro danh giá",
"settings.language": "Ngôn ngữ",
"settings.languagePicker": "Ngôn ngữ ứng dụng:",
"settings.languageDesc": "Chọn ngôn ngữ dùng cho menu và cài đặt.",
diff --git a/src/Apptag/Localization/zh-Hans.json b/src/Apptag/Localization/zh-Hans.json
index 679c46e..94b0c07 100644
--- a/src/Apptag/Localization/zh-Hans.json
+++ b/src/Apptag/Localization/zh-Hans.json
@@ -8,6 +8,8 @@
"menu.language": "语言",
"menu.help": "帮助",
"menu.version": "版本",
+ "menu.proStatus.free": "当前您是:免费版(升级到 Pro)用户",
+ "menu.proStatus.unlocked": "当前您是:尊贵的 Pro 版用户",
"settings.language": "语言",
"settings.languagePicker": "应用语言:",
"settings.languageDesc": "选择菜单和设置面板使用的语言。",
diff --git a/src/Apptag/Localization/zh-Hant.json b/src/Apptag/Localization/zh-Hant.json
index c205797..892f5c0 100644
--- a/src/Apptag/Localization/zh-Hant.json
+++ b/src/Apptag/Localization/zh-Hant.json
@@ -8,6 +8,8 @@
"menu.language": "語言",
"menu.help": "說明",
"menu.version": "版本",
+ "menu.proStatus.free": "目前您是:免費版(升級到 Pro)使用者",
+ "menu.proStatus.unlocked": "目前您是:尊貴的 Pro 版使用者",
"settings.language": "語言",
"settings.languagePicker": "應用程式語言:",
"settings.languageDesc": "選擇選單和設定面板使用的語言。",
diff --git a/src/Apptag/PreferencesView.swift b/src/Apptag/PreferencesView.swift
index 0ac3ceb..52b09ab 100644
--- a/src/Apptag/PreferencesView.swift
+++ b/src/Apptag/PreferencesView.swift
@@ -578,7 +578,7 @@
}
switch proEntitlement.accessState {
case .free:
- return .locked
+ return .neutral
case .purchased:
return .unlocked
case .legacy:
@@ -803,7 +803,7 @@
)
}
- private var dataProStatusCard: some View {
+ private var proStatusCard: some View {
HStack(alignment: .top, spacing: 18) {
VStack(alignment: .leading, spacing: 10) {
HStack(spacing: 10) {
@@ -1139,8 +1139,6 @@
Spacer(minLength: 36)
VStack(alignment: .center, spacing: 24) {
- dataProStatusCard
-
dataSection(minHeight: 124) {
HStack(alignment: .center, spacing: 12) {
dataSectionTitle(tr("settings.initialLayout"))
@@ -1291,62 +1289,67 @@
case .about:
// Tab 6: About
VStack(spacing: 0) {
- Spacer(minLength: 72)
+ Spacer(minLength: 44)
- 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(alignment: .center, spacing: 24) {
+ proStatusCard
- Divider()
- .padding(.vertical, 4)
-
- HStack(spacing: 8) {
- Button {
- NSWorkspace.shared.open(helpPDFURL)
- } label: {
- Label(tr("help.openPDF"), systemImage: "questionmark.circle")
- }
- .buttonStyle(.borderedProminent)
- .controlSize(.regular)
-
- Button {
- NSPasteboard.general.clearContents()
- NSPasteboard.general.setString(helpPDFURL.absoluteString, forType: .string)
- } label: {
- Image(systemName: "link")
- }
- .buttonStyle(.bordered)
- .controlSize(.regular)
- .help(tr("help.copyLink"))
+ HStack(alignment: .center, spacing: 24) {
+ if let icon = NSImage(named: NSImage.applicationIconName) {
+ Image(nsImage: icon)
+ .resizable()
+ .frame(width: 112, height: 112)
}
+ 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)
- Text(tr("help.currentLanguage"))
- .font(.caption)
- .foregroundStyle(.secondary)
- .padding(.bottom, 2)
+ Divider()
+ .padding(.vertical, 4)
- Text("万物之中,希望最美")
- .font(.caption)
- .foregroundStyle(.secondary)
- Text("永桔@shanghai3168@gmail.com")
- .font(.caption)
- .foregroundStyle(.secondary)
+ HStack(spacing: 8) {
+ Button {
+ NSWorkspace.shared.open(helpPDFURL)
+ } label: {
+ Label(tr("help.openPDF"), systemImage: "questionmark.circle")
+ }
+ .buttonStyle(.borderedProminent)
+ .controlSize(.regular)
+
+ Button {
+ NSPasteboard.general.clearContents()
+ NSPasteboard.general.setString(helpPDFURL.absoluteString, forType: .string)
+ } label: {
+ Image(systemName: "link")
+ }
+ .buttonStyle(.bordered)
+ .controlSize(.regular)
+ .help(tr("help.copyLink"))
+ }
+
+ Text(tr("help.currentLanguage"))
+ .font(.caption)
+ .foregroundStyle(.secondary)
+ .padding(.bottom, 2)
+
+ Text("万物之中,希望最美")
+ .font(.caption)
+ .foregroundStyle(.secondary)
+ Text("永桔@shanghai3168@gmail.com")
+ .font(.caption)
+ .foregroundStyle(.secondary)
+ }
}
+ .frame(width: dataPanelWidth, alignment: .leading)
}
- .frame(width: 560, alignment: .leading)
+ .frame(width: dataPanelWidth, alignment: .center)
Spacer(minLength: 30)
Spacer(minLength: 18)
diff --git a/src/Apptag/ProAccessViews.swift b/src/Apptag/ProAccessViews.swift
index 1d4bcd6..51c1daa 100644
--- a/src/Apptag/ProAccessViews.swift
+++ b/src/Apptag/ProAccessViews.swift
@@ -14,7 +14,14 @@
var compact = false
var body: some View {
- Text(text)
+ HStack(spacing: compact ? 4 : 5) {
+ if showsCrown {
+ Image(systemName: "crown.fill")
+ .font(.system(size: compact ? 9 : 10, weight: .bold))
+ }
+
+ Text(text)
+ }
.font(.system(size: compact ? 10 : 11, weight: .semibold))
.foregroundStyle(foregroundColor)
.lineLimit(1)
@@ -33,11 +40,11 @@
private var foregroundColor: Color {
switch style {
case .locked:
- return Color.accentColor
+ return Color(red: 0.56, green: 0.35, blue: 0.03)
case .preview:
return Color(red: 0.30, green: 0.22, blue: 0.08)
case .unlocked, .legacy:
- return Color(red: 0.10, green: 0.42, blue: 0.24)
+ return Color(red: 0.56, green: 0.35, blue: 0.03)
case .neutral:
return .secondary
}
@@ -46,11 +53,11 @@
private var backgroundColor: Color {
switch style {
case .locked:
- return Color.accentColor.opacity(0.10)
+ return Color(red: 1.0, green: 0.78, blue: 0.22).opacity(0.18)
case .preview:
return Color.orange.opacity(0.14)
case .unlocked, .legacy:
- return Color.green.opacity(0.12)
+ return Color(red: 1.0, green: 0.78, blue: 0.22).opacity(0.18)
case .neutral:
return Color.secondary.opacity(0.10)
}
@@ -59,15 +66,24 @@
private var strokeColor: Color {
switch style {
case .locked:
- return Color.accentColor.opacity(0.26)
+ return Color(red: 0.93, green: 0.62, blue: 0.10).opacity(0.42)
case .preview:
return Color.orange.opacity(0.28)
case .unlocked, .legacy:
- return Color.green.opacity(0.26)
+ return Color(red: 0.93, green: 0.62, blue: 0.10).opacity(0.42)
case .neutral:
return Color.secondary.opacity(0.18)
}
}
+
+ private var showsCrown: Bool {
+ switch style {
+ case .locked, .unlocked, .legacy:
+ return text.localizedCaseInsensitiveContains("Pro")
+ case .preview, .neutral:
+ return false
+ }
+ }
}
extension ProFeature {
diff --git a/src/Apptag/QuickSearch.swift b/src/Apptag/QuickSearch.swift
index ccbf454..5df8d3b 100644
--- a/src/Apptag/QuickSearch.swift
+++ b/src/Apptag/QuickSearch.swift
@@ -1432,6 +1432,15 @@
trackingAreaRef = area
}
+ override func hitTest(_ point: NSPoint) -> NSView? {
+ guard !isHidden, alphaValue > 0, bounds.contains(point) else { return nil }
+ return self
+ }
+
+ override func acceptsFirstMouse(for event: NSEvent?) -> Bool {
+ true
+ }
+
override func mouseEntered(with event: NSEvent) {
super.mouseEntered(with: event)
if let result {
diff --git a/src/CHANGELOG.md b/src/CHANGELOG.md
index 5ec896c..407ba80 100644
--- a/src/CHANGELOG.md
+++ b/src/CHANGELOG.md
@@ -2,6 +2,16 @@
## [Unreleased]
+## [8.1.1] — 2026-06-26
+
+- 优化样式 2 / 样式 3 的 Mac 自带容器布局:瀑布流容器样式下固定到底部并横向占满,避免窄列过高挤压其它分组
+- 调整 Pro 状态入口:升级卡从“数据”页迁移到“关于”页,数据页重新专注于分类方案、导入导出和气泡显示位置设置
+- 菜单栏下拉项新增当前免费 / Pro 身份状态:免费用户可点击进入“关于”页升级;Pro 用户显示金色皇冠图标和 Pro 身份文案
+- 统一 Pro 胶囊为金色皇冠体系;免费版状态保持中性胶囊,避免把免费状态误读为已解锁 Pro
+- 修复窗口回归中发现的 Quick Search 点击边界:结果行内文字 / 图标区域也能稳定触发启动和关闭;点击 TagLauncher 自身结果时只关闭当前浮层,不再通过外部激活把 AppGrid 重新拉起
+- 补齐 29 个语言包的菜单栏 Pro 状态文案,并修正土耳其语免费用户文案 typo
+- 版本号更新为 `8.1.1`,Build 更新为 `20260626.2229`
+
## [8.1.0] — 2026-06-26
- 新增免费版 + Pro 一次性买断解锁体系:核心标签整理、基础启动和 Quick Search 保持免费可用,高级主题、导入 / 导出布局、无限自定义备注和应用图标排序持久化纳入 Pro
diff --git a/src/Scripts/window_logic_qa.sh b/src/Scripts/window_logic_qa.sh
index 81962ab..7149bde 100755
--- a/src/Scripts/window_logic_qa.sh
+++ b/src/Scripts/window_logic_qa.sh
@@ -7,10 +7,14 @@
LAUNCH_AGENT_LABEL="com.taglauncher.app"
LAUNCH_AGENT_PLIST="$HOME/Library/LaunchAgents/$LAUNCH_AGENT_LABEL.plist"
SAVED_STATE_DIR="$HOME/Library/Saved Application State/$LAUNCH_AGENT_LABEL.savedState"
+STORE_DIR="$HOME/Library/Application Support/TagLauncher"
+STORE_PATH="$STORE_DIR/tags.json"
USER_GUI_DOMAIN="gui/$(id -u)"
RESTORE_LAUNCH_AGENT=false
LAUNCH_AGENT_PLIST_WAS_PRESENT=false
LAUNCH_AGENT_BACKUP="$(mktemp -t taglauncher-launchagent.XXXXXX.plist)"
+STORE_WAS_PRESENT=false
+STORE_BACKUP="$(mktemp -t taglauncher-tags.XXXXXX.json)"
DEFAULTS_DOMAIN="$LAUNCH_AGENT_LABEL"
SHOW_DOCK_ICON_WAS_SET=false
SHOW_DOCK_ICON_VALUE=""
@@ -46,6 +50,13 @@
if [[ -f "$LAUNCH_AGENT_PLIST" ]]; then
LAUNCH_AGENT_PLIST_WAS_PRESENT=true
cp "$LAUNCH_AGENT_PLIST" "$LAUNCH_AGENT_BACKUP"
+ fi
+}
+
+backup_store() {
+ if [[ -f "$STORE_PATH" ]]; then
+ STORE_WAS_PRESENT=true
+ cp "$STORE_PATH" "$STORE_BACKUP"
fi
}
@@ -85,6 +96,16 @@
fi
else
rm -f "$LAUNCH_AGENT_PLIST"
+ fi
+}
+
+restore_store() {
+ kill_all_taglauncher_instances
+ if [[ "$STORE_WAS_PRESENT" == true ]]; then
+ mkdir -p "$STORE_DIR"
+ cp "$STORE_BACKUP" "$STORE_PATH" || true
+ else
+ rm -f "$STORE_PATH"
fi
}
@@ -239,6 +260,7 @@
restore_user_defaults
restore_qa_environment
restore_launch_agent_plist
+ restore_store
if [[ "$RESTORE_LAUNCH_AGENT" == true && -f "$LAUNCH_AGENT_PLIST" ]]; then
launchctl bootstrap "$USER_GUI_DOMAIN" "$LAUNCH_AGENT_PLIST" >/dev/null 2>&1 || true
fi
@@ -246,6 +268,7 @@
trap cleanup EXIT
backup_user_defaults
backup_launch_agent_plist
+backup_store
backup_qa_environment
kill_all_taglauncher_instances() {
@@ -458,12 +481,60 @@
assert_single_qa_app_instance
}
+seed_quick_search_recent_result() {
+ local app_path=""
+ for candidate in \
+ "/System/Applications/System Settings.app" \
+ "/System/Applications/Utilities/Terminal.app" \
+ "/System/Applications/Calculator.app" \
+ "/System/Applications/TextEdit.app"
+ do
+ if [[ -d "$candidate" ]]; then
+ app_path="$candidate"
+ break
+ fi
+ done
+
+ if [[ -z "$app_path" ]]; then
+ echo "FAIL: could not find a stable system app to seed Quick Search history" >&2
+ exit 1
+ fi
+
+ mkdir -p "$STORE_DIR"
+ python3 - "$STORE_PATH" "$app_path" <<'PY'
+import json
+import os
+import sys
+from datetime import datetime, timezone
+
+store_path, app_path = sys.argv[1], sys.argv[2]
+if os.path.exists(store_path):
+ with open(store_path, "r", encoding="utf-8") as handle:
+ try:
+ store = json.load(handle)
+ except json.JSONDecodeError:
+ store = {}
+else:
+ store = {}
+
+store.setdefault("version", 1)
+store.setdefault("appOpenCounts", {})
+store.setdefault("appLastOpenedAt", {})
+store["appOpenCounts"][app_path] = max(1, int(store["appOpenCounts"].get(app_path, 0)) + 1)
+reference = datetime(2001, 1, 1, tzinfo=timezone.utc)
+store["appLastOpenedAt"][app_path] = (datetime.now(timezone.utc) - reference).total_seconds()
+
+with open(store_path, "w", encoding="utf-8") as handle:
+ json.dump(store, handle, ensure_ascii=False, indent=2, sort_keys=True)
+PY
+}
+
assert_swift="$(mktemp -t taglauncher-window-assert.XXXXXX.swift)"
coords_swift="$(mktemp -t taglauncher-window-coords.XXXXXX.swift)"
settings_ax_swift="$(mktemp -t taglauncher-settings-ax.XXXXXX.swift)"
screens_swift="$(mktemp -t taglauncher-screens.XXXXXX.swift)"
fullscreen_swift="$(mktemp -t taglauncher-fullscreen-target.XXXXXX.swift)"
-trap 'cleanup; rm -f "$assert_swift" "$coords_swift" "$settings_ax_swift" "$screens_swift" "$fullscreen_swift" "$LAUNCH_AGENT_BACKUP"' EXIT
+trap 'cleanup; rm -f "$assert_swift" "$coords_swift" "$settings_ax_swift" "$screens_swift" "$fullscreen_swift" "$LAUNCH_AGENT_BACKUP" "$STORE_BACKUP"' EXIT
cat >"$assert_swift" <<'SWIFT'
import AppKit
@@ -1422,6 +1493,7 @@
kill_all_taglauncher_instances
sleep 0.4
swift_assert no-overlay
+seed_quick_search_recent_result
prepare_isolated_app_instance
assert_no_dock_tile
log "==> QA hidden Dock: clicking Quick Search result closes without showing App Grid"
--
Gitblit v1.9.3