| src/Apptag/Info.plist | ●●●●● patch | view | raw | blame | history | |
| src/Apptag/PreferencesView.swift | ●●●●● patch | view | raw | blame | history | |
| src/CHANGELOG.md | ●●●●● patch | view | raw | blame | history | |
| src/Release/AppStore-8.2.1-20260628.2354/Archive/SHA256SUMS.txt | ●●●●● patch | view | raw | blame | history | |
| src/Release/AppStore-8.2.1-20260628.2354/Archive/TagLauncher-8.2.1-build20260628.2354.dmg | patch | view | raw | blame | history | |
| src/Release/AppStore-8.2.1-20260628.2354/QA/QA-Report.md | ●●●●● patch | view | raw | blame | history | |
| src/Release/AppStore-8.2.1-20260628.2354/RELEASE_MANIFEST.md | ●●●●● patch | view | raw | blame | history | |
| src/Scripts/pro_custom_hotkeys_qa.sh | ●●●●● patch | view | raw | blame | history |
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> 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,8 +1730,7 @@ let statusTone: HotkeyStatusTone var body: some View { VStack(alignment: .leading, spacing: 12) { HStack(alignment: .top, spacing: 12) { HStack(alignment: .center, spacing: 18) { VStack(alignment: .leading, spacing: 3) { Text(title) .font(.system(size: 13, weight: .semibold)) @@ -1740,20 +1741,16 @@ } .frame(maxWidth: .infinity, alignment: .leading) VStack(alignment: .leading, spacing: 4) { HStack(alignment: .center, spacing: 12) { 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: 156, alignment: .leading) HotkeyStatusPill(text: statusText, tone: statusTone) } .frame(width: 180, alignment: .leading) } .frame(width: 310, alignment: .leading) } .padding(12) .background( @@ -1776,13 +1773,13 @@ 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) { HStack(alignment: .center, spacing: 18) { VStack(alignment: .leading, spacing: 3) { Text(title) .font(.system(size: 13, weight: .semibold)) @@ -1793,19 +1790,15 @@ } .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) } } } .frame(width: 156, alignment: .leading) HStack(spacing: 8) { Button(customizeTitle, action: onCustomize) .buttonStyle(.borderedProminent) .controlSize(.small) HotkeyPrimaryActionButton( title: customizeTitle, isProLocked: isProLocked, action: onCustomize ) Button(restoreTitle, action: onRestore) .buttonStyle(.bordered) .controlSize(.small) .disabled(!canRestore) if canRestore { HotkeySecondaryActionButton( title: restoreTitle, action: onRestore ) } } .frame(width: 230, alignment: .leading) } .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 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 全局唤起快捷键 src/Release/AppStore-8.2.1-20260628.2354/Archive/SHA256SUMS.txt
New file @@ -0,0 +1 @@ f166743d18af08225056a77bbc61dd39c5ee4bd264273786d094bf941b18c271 src/Release/AppStore-8.2.1-20260628.2354/Archive/TagLauncher-8.2.1-build20260628.2354.dmg src/Release/AppStore-8.2.1-20260628.2354/Archive/TagLauncher-8.2.1-build20260628.2354.dmgBinary files differ
src/Release/AppStore-8.2.1-20260628.2354/QA/QA-Report.md
New file @@ -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` src/Release/AppStore-8.2.1-20260628.2354/RELEASE_MANIFEST.md
New file @@ -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`。 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