From 18c90acda54cb46815a081ecfc36ea8f2b9e93fd Mon Sep 17 00:00:00 2001
From: Ariver <shanghai3168@gmail.com>
Date: Mon, 29 Jun 2026 00:12:04 +0800
Subject: [PATCH] Fix custom hotkey settings UI for 8.2.1
---
src/Release/AppStore-8.2.1-20260628.2354/QA/QA-Report.md | 61 ++++++++++++
src/Release/AppStore-8.2.1-20260628.2354/RELEASE_MANIFEST.md | 25 +++++
src/Apptag/PreferencesView.swift | 170 +++++++++++++++++++++++-----------
src/CHANGELOG.md | 8 +
src/Release/AppStore-8.2.1-20260628.2354/Archive/SHA256SUMS.txt | 1
src/Apptag/Info.plist | 4
src/Release/AppStore-8.2.1-20260628.2354/Archive/TagLauncher-8.2.1-build20260628.2354.dmg | 0
src/Scripts/pro_custom_hotkeys_qa.sh | 13 ++
8 files changed, 224 insertions(+), 58 deletions(-)
diff --git a/src/Apptag/Info.plist b/src/Apptag/Info.plist
index 6435f10..0d72ecf 100644
--- a/src/Apptag/Info.plist
+++ b/src/Apptag/Info.plist
@@ -19,7 +19,7 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
- <string>8.2.0</string>
+ <string>8.2.1</string>
<key>CFBundleURLTypes</key>
<array>
<dict>
@@ -32,7 +32,7 @@
</dict>
</array>
<key>CFBundleVersion</key>
- <string>20260628.2308</string>
+ <string>20260628.2354</string>
<key>LSApplicationCategoryType</key>
<string>public.app-category.utilities</string>
<key>LSMinimumSystemVersion</key>
diff --git a/src/Apptag/PreferencesView.swift b/src/Apptag/PreferencesView.swift
index 41fec66..8838d3d 100644
--- a/src/Apptag/PreferencesView.swift
+++ b/src/Apptag/PreferencesView.swift
@@ -944,6 +944,7 @@
customizeTitle: proEntitlement.isUnlocked ? tr("hotkeys.customize") : tr("pro.card.badge"),
restoreTitle: tr("hotkeys.restoreDefault"),
isRecording: recordingHotkeyKind == .main,
+ isProLocked: !proEntitlement.isUnlocked,
canRestore: proEntitlement.isUnlocked && LauncherHotkeySettings.hasCustomHotkey(for: .main),
onCustomize: { startHotkeyRecording(for: .main) },
onRestore: { restoreDefaultHotkey(for: .main) }
@@ -966,6 +967,7 @@
customizeTitle: proEntitlement.isUnlocked ? tr("hotkeys.customize") : tr("pro.card.badge"),
restoreTitle: tr("hotkeys.restoreDefault"),
isRecording: recordingHotkeyKind == .quickSearch,
+ isProLocked: !proEntitlement.isUnlocked,
canRestore: proEntitlement.isUnlocked && LauncherHotkeySettings.hasCustomHotkey(for: .quickSearch),
onCustomize: { startHotkeyRecording(for: .quickSearch) },
onRestore: { restoreDefaultHotkey(for: .quickSearch) }
@@ -1728,32 +1730,27 @@
let statusTone: HotkeyStatusTone
var body: some View {
- VStack(alignment: .leading, spacing: 12) {
- HStack(alignment: .top, spacing: 12) {
- VStack(alignment: .leading, spacing: 3) {
- Text(title)
- .font(.system(size: 13, weight: .semibold))
- Text(description)
- .font(.caption)
- .foregroundStyle(.secondary)
- .fixedSize(horizontal: false, vertical: true)
- }
- .frame(maxWidth: .infinity, alignment: .leading)
-
- VStack(alignment: .leading, spacing: 4) {
- Text(displayText)
- .font(.system(size: 15, weight: .semibold))
- .lineLimit(1)
- .minimumScaleFactor(0.85)
- Text(statusText)
- .font(.system(size: 11, weight: .semibold))
- .foregroundStyle(statusTone.foreground)
- .padding(.horizontal, 8)
- .padding(.vertical, 3)
- .background(Capsule().fill(statusTone.background))
- }
- .frame(width: 180, alignment: .leading)
+ HStack(alignment: .center, spacing: 18) {
+ VStack(alignment: .leading, spacing: 3) {
+ Text(title)
+ .font(.system(size: 13, weight: .semibold))
+ Text(description)
+ .font(.caption)
+ .foregroundStyle(.secondary)
+ .fixedSize(horizontal: false, vertical: true)
}
+ .frame(maxWidth: .infinity, alignment: .leading)
+
+ HStack(alignment: .center, spacing: 12) {
+ Text(displayText)
+ .font(.system(size: 15, weight: .semibold))
+ .lineLimit(1)
+ .minimumScaleFactor(0.85)
+ .frame(width: 156, alignment: .leading)
+
+ HotkeyStatusPill(text: statusText, tone: statusTone)
+ }
+ .frame(width: 310, alignment: .leading)
}
.padding(12)
.background(
@@ -1776,36 +1773,32 @@
let customizeTitle: String
let restoreTitle: String
let isRecording: Bool
+ let isProLocked: Bool
let canRestore: Bool
let onCustomize: () -> Void
let onRestore: () -> Void
var body: some View {
- VStack(alignment: .leading, spacing: 12) {
- HStack(alignment: .top, spacing: 12) {
- VStack(alignment: .leading, spacing: 3) {
- Text(title)
- .font(.system(size: 13, weight: .semibold))
- Text(description)
- .font(.caption)
- .foregroundStyle(.secondary)
- .fixedSize(horizontal: false, vertical: true)
- }
- .frame(maxWidth: .infinity, alignment: .leading)
+ HStack(alignment: .center, spacing: 18) {
+ VStack(alignment: .leading, spacing: 3) {
+ Text(title)
+ .font(.system(size: 13, weight: .semibold))
+ Text(description)
+ .font(.caption)
+ .foregroundStyle(.secondary)
+ .fixedSize(horizontal: false, vertical: true)
+ }
+ .frame(maxWidth: .infinity, alignment: .leading)
- VStack(alignment: .leading, spacing: 7) {
+ HStack(alignment: .center, spacing: 12) {
+ VStack(alignment: .leading, spacing: 6) {
Text(isRecording ? "…" : displayText)
.font(.system(size: 15, weight: .semibold))
.lineLimit(1)
.minimumScaleFactor(0.85)
HStack(spacing: 6) {
- Text(statusText)
- .font(.system(size: 11, weight: .semibold))
- .foregroundStyle(statusTone.foreground)
- .padding(.horizontal, 8)
- .padding(.vertical, 3)
- .background(Capsule().fill(statusTone.background))
+ HotkeyStatusPill(text: statusText, tone: statusTone)
if isRecording {
Text(tr("hotkeys.recordingInline"))
@@ -1813,20 +1806,23 @@
.foregroundStyle(.blue)
}
}
-
- HStack(spacing: 8) {
- Button(customizeTitle, action: onCustomize)
- .buttonStyle(.borderedProminent)
- .controlSize(.small)
-
- Button(restoreTitle, action: onRestore)
- .buttonStyle(.bordered)
- .controlSize(.small)
- .disabled(!canRestore)
- }
}
- .frame(width: 230, alignment: .leading)
+ .frame(width: 156, alignment: .leading)
+
+ HotkeyPrimaryActionButton(
+ title: customizeTitle,
+ isProLocked: isProLocked,
+ action: onCustomize
+ )
+
+ if canRestore {
+ HotkeySecondaryActionButton(
+ title: restoreTitle,
+ action: onRestore
+ )
+ }
}
+ .frame(width: 310, alignment: .leading)
}
.padding(12)
.background(
@@ -1840,6 +1836,70 @@
}
}
+private struct HotkeyStatusPill: View {
+ let text: String
+ let tone: HotkeyStatusTone
+
+ var body: some View {
+ Text(text)
+ .font(.system(size: 11, weight: .semibold))
+ .foregroundStyle(tone.foreground)
+ .lineLimit(1)
+ .padding(.horizontal, 8)
+ .padding(.vertical, 3)
+ .background(Capsule().fill(tone.background))
+ }
+}
+
+private struct HotkeyPrimaryActionButton: View {
+ let title: String
+ let isProLocked: Bool
+ let action: () -> Void
+
+ var body: some View {
+ Button(action: action) {
+ if isProLocked {
+ ProStatusPill(text: title, style: .locked, compact: true)
+ } else {
+ Text(title)
+ .font(.system(size: 11, weight: .semibold))
+ .foregroundStyle(Color.white)
+ .lineLimit(1)
+ .padding(.horizontal, 12)
+ .padding(.vertical, 6)
+ .background(
+ Capsule(style: .continuous)
+ .fill(Color.accentColor)
+ )
+ }
+ }
+ .buttonStyle(.plain)
+ .contentShape(Capsule(style: .continuous))
+ }
+}
+
+private struct HotkeySecondaryActionButton: View {
+ let title: String
+ let action: () -> Void
+
+ var body: some View {
+ Button(action: action) {
+ Text(title)
+ .font(.system(size: 11, weight: .semibold))
+ .foregroundStyle(.secondary)
+ .lineLimit(1)
+ .padding(.horizontal, 12)
+ .padding(.vertical, 6)
+ .background(
+ Capsule(style: .continuous)
+ .fill(Color.secondary.opacity(0.12))
+ )
+ }
+ .buttonStyle(.plain)
+ .contentShape(Capsule(style: .continuous))
+ }
+}
+
private struct SettingsConfirmationView: View {
let title: String
let message: String
diff --git a/src/CHANGELOG.md b/src/CHANGELOG.md
index dadea7f..9aee5ca 100644
--- a/src/CHANGELOG.md
+++ b/src/CHANGELOG.md
@@ -2,6 +2,14 @@
## [Unreleased]
+## [8.2.1] — 2026-06-28
+
+- 修复“设置 > 快捷键”页的自定义快捷键行排版:快捷键值、状态和操作入口改为同一行稳定布局,避免右侧控件纵向堆叠导致页面失衡
+- 自定义快捷键未解锁入口统一改为全局皇冠 Pro pill,不再显示与其他 Pro 功能不一致的蓝色 `Pro` 按钮
+- 未保存自定义快捷键时不再显示灰掉的“恢复默认”按钮,减少无效控件干扰;保存自定义值后仍可恢复默认
+- 明确本机验收方式:使用 QA 环境变量 `TAGLAUNCHER_QA_PRO_STATE=pro` 可在不走真实 App Store 购买的情况下验证 Pro 自定义快捷键,关闭该变量后恢复真实权益状态
+- 版本号更新为 `8.2.1`,Build 更新为 `20260628.2354`
+
## [8.2.0] — 2026-06-28
- 新增 Pro 自定义快捷键:Pro 用户可在“设置 > 快捷键”分别修改 App Grid 全局唤起和 Quick Search 全局唤起快捷键
diff --git a/src/Release/AppStore-8.2.1-20260628.2354/Archive/SHA256SUMS.txt b/src/Release/AppStore-8.2.1-20260628.2354/Archive/SHA256SUMS.txt
new file mode 100644
index 0000000..49a578a
--- /dev/null
+++ b/src/Release/AppStore-8.2.1-20260628.2354/Archive/SHA256SUMS.txt
@@ -0,0 +1 @@
+f166743d18af08225056a77bbc61dd39c5ee4bd264273786d094bf941b18c271 src/Release/AppStore-8.2.1-20260628.2354/Archive/TagLauncher-8.2.1-build20260628.2354.dmg
diff --git a/src/Release/AppStore-8.2.1-20260628.2354/Archive/TagLauncher-8.2.1-build20260628.2354.dmg b/src/Release/AppStore-8.2.1-20260628.2354/Archive/TagLauncher-8.2.1-build20260628.2354.dmg
new file mode 100644
index 0000000..9c04c34
--- /dev/null
+++ b/src/Release/AppStore-8.2.1-20260628.2354/Archive/TagLauncher-8.2.1-build20260628.2354.dmg
Binary files differ
diff --git a/src/Release/AppStore-8.2.1-20260628.2354/QA/QA-Report.md b/src/Release/AppStore-8.2.1-20260628.2354/QA/QA-Report.md
new file mode 100644
index 0000000..d71b471
--- /dev/null
+++ b/src/Release/AppStore-8.2.1-20260628.2354/QA/QA-Report.md
@@ -0,0 +1,61 @@
+# QA Report - TagLauncher 8.2.1 (20260628.2354)
+
+日期:2026-06-28
+
+## 范围
+
+- 修复“设置 > 快捷键”页自定义快捷键行 UI 排版。
+- 将未解锁自定义快捷键入口统一为全局皇冠 Pro pill。
+- 隐藏无效的“恢复默认”按钮,仅在存在自定义快捷键时显示。
+- 明确本机 QA 解锁 Pro 的验证方式,不改真实购买流程。
+
+## 验证命令
+
+```bash
+bash src/Scripts/pro_custom_hotkeys_qa.sh
+bash src/Scripts/pro_feature_gate_qa.sh
+bash src/Scripts/pro_localization_qa.sh
+bash src/Scripts/macos14_availability_typecheck_qa.sh
+bash src/Scripts/macos14_build_metadata_qa.sh
+APP_BUILD=20260628.2354 bash build.sh
+APP_BUILD=20260628.2354 bash make_dmg.sh
+codesign --verify --deep --strict --verbose=2 build/TagLauncher.app
+hdiutil verify build/TagLauncher.dmg
+```
+
+## 结果
+
+- Pro custom hotkeys QA:通过。
+- Pro feature gate QA:通过。
+- Pro localization QA:通过,29 个语种、49 个 Pro keys。
+- macOS 14 availability typecheck:通过。
+- macOS 14 build metadata:通过,`LSMinimumSystemVersion=14.0`,`minos=14.0`,`arm64`。
+- App build:通过。
+- DMG build:通过。
+- codesign verify:通过。
+- hdiutil verify:通过。
+
+## 本机 Pro 验收方式
+
+真实 App Store 购买不可用时,可以用 QA 环境变量验证 Pro 自定义快捷键,不写入真实购买记录。
+
+启用 QA Pro:
+
+```bash
+launchctl setenv TAGLAUNCHER_QA_PRO_STATE pro
+osascript -e 'quit app "TagLauncher"' >/dev/null 2>&1 || true
+open -a /Applications/TagLauncher.app
+```
+
+恢复真实权益状态:
+
+```bash
+launchctl unsetenv TAGLAUNCHER_QA_PRO_STATE
+osascript -e 'quit app "TagLauncher"' >/dev/null 2>&1 || true
+open -a /Applications/TagLauncher.app
+```
+
+## 产物
+
+- DMG:`src/Release/AppStore-8.2.1-20260628.2354/Archive/TagLauncher-8.2.1-build20260628.2354.dmg`
+- SHA256:`f166743d18af08225056a77bbc61dd39c5ee4bd264273786d094bf941b18c271`
diff --git a/src/Release/AppStore-8.2.1-20260628.2354/RELEASE_MANIFEST.md b/src/Release/AppStore-8.2.1-20260628.2354/RELEASE_MANIFEST.md
new file mode 100644
index 0000000..f862a84
--- /dev/null
+++ b/src/Release/AppStore-8.2.1-20260628.2354/RELEASE_MANIFEST.md
@@ -0,0 +1,25 @@
+# Release Manifest - TagLauncher 8.2.1 (20260628.2354)
+
+日期:2026-06-28
+
+## 版本
+
+- App Version:8.2.1
+- Build:20260628.2354
+- 基线:8.2.0 / 20260628.2308
+
+## 变更
+
+- 修复快捷键设置页右侧操作区排版混乱。
+- 自定义快捷键锁定入口统一使用皇冠 Pro pill。
+- 未存在自定义快捷键时隐藏“恢复默认”按钮。
+- 记录本机 QA Pro 解锁验收流程,避免依赖真实购买。
+
+## 安装包
+
+- `Archive/TagLauncher-8.2.1-build20260628.2354.dmg`
+- SHA256:`f166743d18af08225056a77bbc61dd39c5ee4bd264273786d094bf941b18c271`
+
+## QA
+
+详见 `QA/QA-Report.md`。
diff --git a/src/Scripts/pro_custom_hotkeys_qa.sh b/src/Scripts/pro_custom_hotkeys_qa.sh
index d725620..bb797a4 100755
--- a/src/Scripts/pro_custom_hotkeys_qa.sh
+++ b/src/Scripts/pro_custom_hotkeys_qa.sh
@@ -150,6 +150,17 @@
require(preferences, "StaticHotkeyInfoRow(", "internal App Grid Space shortcut must remain static")
require(preferences, "LauncherHotkeySettings.effectiveHotkey(for: .main).displayString", "Settings must display effective App Grid shortcut")
require(preferences, "LauncherHotkeySettings.effectiveHotkey(for: .quickSearch).displayString", "Settings must display effective Quick Search shortcut")
+require(preferences, "HotkeyPrimaryActionButton", "custom hotkey rows must use the shared action button")
+require(preferences, "ProStatusPill(text: title, style: .locked, compact: true)", "locked custom hotkey action must use the global Pro pill")
+require(preferences, "if canRestore {", "restore-default action must only appear when a custom shortcut exists")
+
+custom_row_start = preferences.find("private struct CustomizableHotkeyInfoRow")
+custom_row_end = preferences.find("private struct HotkeyStatusPill")
+if custom_row_start == -1 or custom_row_end == -1 or custom_row_end <= custom_row_start:
+ fail("could not inspect CustomizableHotkeyInfoRow layout")
+custom_row = preferences[custom_row_start:custom_row_end]
+if ".buttonStyle(.borderedProminent)" in custom_row:
+ fail("custom hotkey rows must not use the default blue borderedProminent Pro button")
require(pro, "case customHotkeys", "ProFeature.customHotkeys is missing")
require(pro, "case .premiumThemes, .layoutImport, .layoutExport, .unlimitedNotes, .persistentAppSorting, .customHotkeys:", "customHotkeys must be unlocked only by the Pro access state")
@@ -175,5 +186,5 @@
if "%status%" not in data["hotkeys.error.registrationFailed"]:
fail(f"{path.name} hotkeys.error.registrationFailed must keep %status% placeholder")
-print("PASS Pro custom hotkeys QA: Pro gating, transactional Carbon registration, duplicate-safe effective shortcuts, local recording, and 29-language copy are wired")
+print("PASS Pro custom hotkeys QA: Pro gating, transactional Carbon registration, duplicate-safe effective shortcuts, local recording, Pro-consistent row UI, and 29-language copy are wired")
PY
--
Gitblit v1.9.3