From 01bab5422712299b49f656e6b4c42a3bf314f519 Mon Sep 17 00:00:00 2001
From: Ariver <shanghai3168@gmail.com>
Date: Thu, 02 Jul 2026 11:02:42 +0800
Subject: [PATCH] Release 8.3.5 free tag order persistence
---
src/Apptag/PreferencesView.swift | 9 ---
src/CHANGELOG.md | 6 ++
src/Apptag/Info.plist | 4
src/Apptag/ContentView.swift | 15 +---
src/Apptag/DataLayer.swift | 1
src/Docs/Requirements/2026-07-01-pro-custom-container-quota-prd.md | 80 ++++++++++++++------------
src/Scripts/pro_custom_container_quota_qa.sh | 45 +++++++++++----
CODEGRAPH.md | 6 +-
8 files changed, 92 insertions(+), 74 deletions(-)
diff --git a/CODEGRAPH.md b/CODEGRAPH.md
index ba545ee..a2bc896 100644
--- a/CODEGRAPH.md
+++ b/CODEGRAPH.md
@@ -42,7 +42,7 @@
- Theme tab 只写视觉偏好 `appGridThemeID` 和炫彩主题调色值,炫彩滑轨只能影响 AppGrid 背景,不得改变标签、排序、备注或分类数据。
- 免费用户预览 Pro 主题时,Theme tab 状态行和当前预览主题卡片显示基于 `ProThemePreviewState.endsAt` 推导的 `MM:SS` 倒计时;倒计时到期只退出预览,不弹阻塞提示。
- Pro tab 展示免费版 / Pro 版对比表;对比表不显示独立标题,Pro 列标题前必须有线型皇冠标识。
- - Pro tab 对比表包含自定义标签颜色、彩色容器 / 彩色网格、高级主题、自定义快捷键、自定义应用备注和数据导入导出 / 分类与布局备份恢复;免费列锁定功能显示“不可用”,彩色容器 / 彩色网格和高级主题显示“只可预览 5 分钟”,备注显示“免费 3 个”;Pro 列未购买时显示带勾的“支持”,备注行显示带勾的“无限备注”,已购买后功能行显示带勾的“已解锁”。表格内免费列和 Pro 列的表头及状态胶囊都在列内左对齐;所有状态胶囊使用与“免费 3 个”一致的中性样式,不显示皇冠图标或黄色 Pro 背景。
+ - Pro tab 对比表包含可维护标签数量、容器内 App 图标位置排序、自定义标签颜色、彩色容器 / 彩色网格、高级主题、自定义快捷键、自定义应用备注和数据导入导出 / 分类与布局备份恢复;标签列表自身顺序不列入 Pro 对比,因为免费用户也可持久保存。免费列锁定功能显示“不可用”,彩色容器 / 彩色网格和高级主题显示“只可预览 5 分钟”,备注显示“免费 3 个”;Pro 列未购买时显示带勾的“支持”,备注行显示带勾的“无限备注”,已购买后功能行显示带勾的“已解锁”。表格内免费列和 Pro 列的表头及状态胶囊都在列内左对齐;所有状态胶囊使用与“免费 3 个”一致的中性样式,不显示皇冠图标或黄色 Pro 背景。
- About tab 保留顶部固定 Pro 状态行;主内容只保留应用信息、帮助文档和联系信息,不放 Pro 汇总卡或免费 / Pro 差异表。
## AppGrid 显示与交互
@@ -202,7 +202,7 @@
- `src/Scripts/pro_localization_qa.sh`
- Pro 文案 29 语种静态门禁;检查 `pro.*`、设置页 Pro 状态/对比表、自定义标签颜色、Quick Search 快捷键页和气泡提示设置键完整、占位符完整、无生成污染和非英文语言无英文兜底复制。
- `src/Scripts/pro_feature_gate_qa.sh`
- - Pro 功能源码门禁;检查 StoreKit 权益、老用户自动 Pro、主题门禁、导入导出硬拦截、3 条备注额度、自定义标签颜色、自定义快捷键、排序预览、Pro UI 提示、设置页线型图标、轻量 Pro 权益行、Pro 页签、免费列“不支持”状态和 About 页不回归 Pro 汇总卡。
+ - Pro 功能源码门禁;检查 StoreKit 权益、老用户自动 Pro、主题门禁、导入导出硬拦截、3 条备注额度、自定义标签颜色、自定义快捷键、容器内 App 排序预览、Pro UI 提示、设置页线型图标、轻量 Pro 权益行、Pro 页签、免费列“不支持”状态和 About 页不回归 Pro 汇总卡。
- `src/Scripts/pro_theme_preview_countdown_qa.sh`
- Pro 主题 / 彩色显示模式预览倒计时门禁;检查状态行倒计时、当前预览卡片 `MM:SS`、到期清理、29 语种 `%time%` 占位符和禁止写死体验时长。
- `src/Scripts/pro_display_mode_preview_qa.sh`
@@ -227,7 +227,7 @@
- 不得擅自改变源码根目录边界:App 源码只在 `src/` 下维护。
- 不得把主题、视觉偏好、窗口状态混入 `TagDatabase` 的分类/标签业务数据。
- 主题变化只允许影响 AppGrid 视觉,不得改变标签数据、排序、备注、SmartStart/category scheme、Quick Search、拖拽语义或导入导出。
-- Pro 门禁不得写脏免费用户真实数据:主题预览不写 `appGridThemeID`,排序预览不写 `containerAppOrder`,第 6 个新备注不写 `appNotes` / `appNoteMetadata`,导入 / 导出拦截必须早于文件面板和文件读写。
+- Pro 门禁不得写脏免费用户真实数据:主题预览不写 `appGridThemeID`,容器内 App 排序预览不写 `containerAppOrder`,第 6 个新备注不写 `appNotes` / `appNoteMetadata`,导入 / 导出拦截必须早于文件面板和文件读写。
- 自定义标签颜色是 Pro 功能:免费用户可继续使用基础色,已有自定义色继续显示,不能新增或修改为新自定义色;改回基础色必须允许且会清除 `customColor`。
- Pro 状态 UI 必须优先使用设置页签下方固定位置的统一 header;不得在各 tab 内重复塞第二条 Pro 引导。About 不承载 Pro 汇总卡或免费 / Pro 差异说明,Data 不承载营销式大卡,避免挤压数据管理布局。
- 免费主题固定为默认、深蓝、黑色;其它主题只能预览或在 Pro 解锁后持久化。
diff --git a/src/Apptag/ContentView.swift b/src/Apptag/ContentView.swift
index 9f2802e..e3a6fe9 100644
--- a/src/Apptag/ContentView.swift
+++ b/src/Apptag/ContentView.swift
@@ -1630,7 +1630,7 @@
.onEnded { _ in
guard canReorderTag(tagName) else { return }
dragItem = nil
- persistTagOrderIfAllowed()
+ persistTagOrder()
}
}
@@ -2669,22 +2669,15 @@
let hadDragState = tagNavDragModeActive || tagNavDragItem != nil
guard hadDragState else { return }
if tagNavDragModeActive && tagNavReorderDidMove {
- persistTagOrderIfAllowed()
+ persistTagOrder()
}
tagNavDragModeActive = false
tagNavDragItem = nil
tagNavReorderDidMove = false
}
- private func persistTagOrderIfAllowed() {
- guard proEntitlement.isUnlocked else {
- showDropWarning(message: tr("pro.tagSorting.previewToast"))
- return
- }
- guard TagEditor.reorderTags(draggedTagNames) else {
- showDropWarning(message: tr("pro.tagSorting.previewToast"))
- return
- }
+ private func persistTagOrder() {
+ _ = TagEditor.reorderTags(draggedTagNames)
}
private func cancelTagNavReorderVisualState() {
diff --git a/src/Apptag/DataLayer.swift b/src/Apptag/DataLayer.swift
index ac1650c..b949e7b 100644
--- a/src/Apptag/DataLayer.swift
+++ b/src/Apptag/DataLayer.swift
@@ -1943,7 +1943,6 @@
/// Persist a new tag display order.
@discardableResult
static func reorderTags(_ names: [String]) -> Bool {
- guard ProEntitlementPolicy.isUnlocked(.persistentAppSorting) else { return false }
var store = TagDatabase.load()
let previousStore = store
store.tagOrder = names
diff --git a/src/Apptag/Info.plist b/src/Apptag/Info.plist
index 307cc59..941795c 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.3.2</string>
+ <string>8.3.5</string>
<key>CFBundleURLTypes</key>
<array>
<dict>
@@ -32,7 +32,7 @@
</dict>
</array>
<key>CFBundleVersion</key>
- <string>20260702.0325</string>
+ <string>20260702.1046</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 a147190..32a95e1 100644
--- a/src/Apptag/PreferencesView.swift
+++ b/src/Apptag/PreferencesView.swift
@@ -1049,15 +1049,6 @@
proUnlockedStyle: .neutral
),
ProTabComparisonFeature(
- title: tr("pro.feature.tagOrderPersistence"),
- freeStatusKey: "settings.proStatus.previewOnly",
- proLockedStatusKey: "settings.proStatus.supported",
- proUnlockedStatusKey: "settings.proStatus.supported",
- freeStyle: .neutral,
- proLockedStyle: .neutral,
- proUnlockedStyle: .neutral
- ),
- ProTabComparisonFeature(
title: tr("pro.feature.containerAppOrderPersistence"),
freeStatusKey: "settings.proStatus.previewOnly",
proLockedStatusKey: "settings.proStatus.supported",
diff --git a/src/CHANGELOG.md b/src/CHANGELOG.md
index e888cec..016a633 100644
--- a/src/CHANGELOG.md
+++ b/src/CHANGELOG.md
@@ -2,6 +2,12 @@
## [Unreleased]
+## [8.3.5] — 2026-07-02
+
+- 标签列表自身顺序不再作为 Pro 权益:免费用户拖动标签后也会持久保存容器显示顺序
+- Pro 页免费 / Pro 对比表移除“自定义排序:标签在列表中的位置”这一行,保留容器内 App 图标位置排序、自定义快捷键等其它 Pro 权益
+- 版本号更新为 `8.3.5`,Build 更新为 `20260702.1046`
+
## [8.3.2] — 2026-07-02
- 修复 App Store 从付费下载切换为免费下载 + Pro 内购时的旧付费用户迁移边界:`8.3.2` 之前通过 App Store 付费下载过 TagLauncher 的用户会自动保留 Pro 权益
diff --git a/src/Docs/Requirements/2026-07-01-pro-custom-container-quota-prd.md b/src/Docs/Requirements/2026-07-01-pro-custom-container-quota-prd.md
index 8a07223..2c5004a 100644
--- a/src/Docs/Requirements/2026-07-01-pro-custom-container-quota-prd.md
+++ b/src/Docs/Requirements/2026-07-01-pro-custom-container-quota-prd.md
@@ -1,18 +1,18 @@
# PRD:自定义标签额度、排序保存与 Pro 转化
日期:2026-07-01
-状态:产品方案讨论稿,待研发评审与实现
+状态:已实现,2026-07-02 按 8.3.5 版本决策修订
适用产品:TagLauncher macOS App
## 1. 功能名
-免费版自定义标签额度、标签排序预览与 Pro 无限容器管理
+免费版自定义标签额度与 Pro 无限容器管理
## 2. 需求描述
TagLauncher 的核心价值是用标签 / 容器管理 Mac 应用。为了提高 Pro 付费转化,需要把 Pro 权益更直接地绑定到核心功能上:免费用户可以体验默认智能分类和基础整理能力,但当用户开始搭建自己的长期容器体系时,应自然触发 Pro 升级理由。
-本需求不采用“维护次数”限制。免费版限制的是可个性化维护的自定义标签 / 容器数量,以及标签拖动排序的保存能力。
+本需求不采用“维护次数”限制。免费版限制的是可个性化维护的自定义标签 / 容器数量;标签列表自身拖动排序不再作为 Pro 权益,免费用户也可以保存容器显示顺序。
## 3. 背景与目标
@@ -26,12 +26,12 @@
| 目标 | 说明 |
|:---|:---|
-| 提升 Pro 转化 | 把升级触点放在用户开始扩展自定义容器体系、希望沉淀长期排序习惯时。 |
+| 提升 Pro 转化 | 把升级触点放在用户开始扩展自定义容器体系、需要容器内 App 图标排序等高级维护能力时。 |
| 保留新用户激活价值 | 默认智能分类仍可免费使用,避免下载后立刻被付费墙挡住。 |
| 规则可解释 | 用户应能理解“免费可维护 6 个自定义容器,Pro 不限”。 |
| 避免数据被扣押 | 老用户和导入数据不能被删除或隐藏;超额时应保留查看和启动能力。 |
| 避免惩罚试错 | 不限制改名、改色等操作次数,避免用户不敢整理。 |
-| 保持体验可感知 | 免费用户可以预览标签拖动排序效果,但保存长期排序结果需要 Pro。 |
+| 保持体验可感知 | 免费用户可以保存标签列表自身顺序;容器内 App 图标排序仍沿用免费预览、Pro 保存的规则。 |
## 4. 术语定义
@@ -43,8 +43,8 @@
| 自定义维护容器 | 用户对默认智能分类进行个性化维护后形成的容器,或用户自建容器。 | 计入 |
| 个性化维护 | 会让默认分类变成用户长期自定义容器的操作,例如改名、改基础颜色、手动调整应用归属。 | 会触发计入 |
| 减法操作 | 删除、隐藏、清理、合并,目的是减少或恢复到免费额度内。 | 不新增额度 |
-| 标签拖动排序预览 | 用户拖动标签并看到临时顺序变化,但该顺序不作为长期结果保存。 | 不计入 |
-| 标签排序保存 | 用户拖动标签后的顺序被保留,并在后续打开、刷新或重启后继续生效。 | Pro 权益 |
+| 标签列表排序保存 | 用户拖动标签后的容器显示顺序被保留,并在后续打开、刷新或重启后继续生效。 | 免费可用 |
+| 容器内 App 图标排序保存 | 用户拖动容器内 App 图标后的顺序被长期保留。 | Pro 权益 |
## 5. 产品结论
@@ -60,7 +60,21 @@
Pro 用户可维护不限数量的自定义标签 / 容器。
-标签拖动排序的保存能力属于 Pro 权益。免费用户可以拖动标签预览排序效果,但不能保存新的标签顺序。
+标签列表自身排序保存能力对免费用户开放。免费用户拖动标签后也会保存新的容器显示顺序。
+
+容器内 App 图标排序保存仍属于 Pro 权益。免费用户可以预览容器内 App 图标排序效果,但不能保存新的容器内图标顺序。
+
+### 5.1.1 2026-07-02 / 8.3.5 决策更新
+
+用户反馈后确认:`自定义排序:标签在列表中的位置(=容器显示位置)` 不再作为 Pro 锁定项。
+
+研发与 QA 口径:
+
+- Pro 对比页删除这一行。
+- `TagEditor.reorderTags` 不再检查 Pro 权益,免费用户也可保存标签列表顺序。
+- `ContentView` 不再在标签拖动结束时展示“仅预览 / 升级 Pro”提示。
+- `自定义排序:容器内 App 图标位置` 仍保留 Pro 权益,不在本次调整中放开。
+- `自定义快捷键` 仍保留 Pro 权益,不在本次调整中放开。
### 5.2 不采用方案
@@ -251,33 +265,27 @@
| 功能 | 免费版 | Pro 版 |
|:---|:---|:---|
| 自定义标签 / 容器维护 | 6 个 | 不限数量 |
-| 标签排序保存 | 仅预览 | 可保存 |
+| 标签列表排序保存 | 可保存 | 可保存 |
+| 容器内 App 图标排序保存 | 仅预览 | 可保存 |
-### 10.7 标签拖动排序保存
+### 10.7 标签列表拖动排序保存
-标签拖动排序应作为独立的 Pro 权益处理,交互参考已实现的“在容器内部拖动改变 App 图标顺序,免费版用户只能预览”。
+标签列表自身拖动排序免费可用。用户拖动标签后,新的容器显示顺序应持久保存。
| 用户状态 | 行为 |
|:---|:---|
-| 免费用户 | 可以拖动标签,并看到临时排序效果。 |
-| 免费用户拖动结束 | 不保存新的标签顺序,展示 Pro 提示。 |
-| 免费用户重新打开 / 刷新 / 触发标签列表重建 | 标签顺序恢复为当前已保存顺序。 |
+| 免费用户 | 可以拖动标签,并保存新的标签顺序。 |
+| 免费用户拖动结束 | 保存新的标签顺序,不展示 Pro 提示。 |
+| 免费用户重新打开 / 刷新 / 触发标签列表重建 | 标签顺序保持为上次保存结果。 |
| Pro 用户 | 可以拖动标签,拖动结束后保存新的标签顺序。 |
| Pro 用户重新打开 / 刷新 / 触发标签列表重建 | 标签顺序保持为上次保存结果。 |
-
-免费用户的提示方式应与现有 App 图标排序预览提示保持一致,不新增独立视觉样式。
-
-建议文案:
-
-> 当前仅为预览。升级 Pro 后可保存标签排序。
交互边界:
- 不在拖动开始前阻断用户。
-- 免费用户拖动过程应保持顺畅,可以看到排序预览。
-- 拖动结束后不保存新的顺序。
+- 免费用户拖动过程应保持顺畅,拖动结束后保存新的顺序。
- 历史已保存的标签顺序不被清空。
-- 本需求不改变容器内部 App 图标排序的现有规则。
+- 本需求不改变容器内部 App 图标排序的现有 Pro 规则。
## 11. 用户旅程
@@ -312,13 +320,13 @@
4. 用户尝试新增第 11 个标签时,提示升级 Pro。
5. 用户可删除或合并标签,回到免费额度内。
-### 11.5 免费用户预览标签排序
+### 11.5 免费用户保存标签列表排序
1. 免费用户希望调整标签展示顺序。
-2. 用户拖动标签,界面展示拖动后的临时排序效果。
+2. 用户拖动标签,界面展示拖动后的排序效果。
3. 用户松手完成拖动。
-4. 系统提示当前仅为预览,保存标签排序需要 Pro。
-5. 用户重新打开、刷新或触发标签列表重建后,标签顺序恢复为当前已保存顺序。
+4. 系统保存新的标签顺序。
+5. 用户重新打开、刷新或触发标签列表重建后,标签顺序保持为上次保存结果。
### 11.6 Pro 用户保存标签排序
@@ -337,8 +345,8 @@
| TAG-QUOTA-04 | 作为免费用户,我希望删除或合并标签后恢复额度,以便继续整理。 | 删除自定义维护容器后释放额度。 |
| TAG-QUOTA-05 | 作为 Pro 用户,我希望不受标签数量限制,以便建立完整应用容器体系。 | Pro 用户可创建和维护不限数量的自定义标签。 |
| TAG-QUOTA-06 | 作为老用户,我希望已有标签不被删除,以便继续访问我的整理结果。 | 超额历史标签仍可查看和启动。 |
-| TAG-SORT-01 | 作为免费用户,我希望能拖动标签看看排序效果,以便判断升级 Pro 后是否有价值。 | 免费用户可拖动标签并看到临时排序效果。 |
-| TAG-SORT-02 | 作为免费用户,我希望知道为什么排序没有保存,以便理解 Pro 权益边界。 | 免费用户拖动结束后看到“仅预览,保存需 Pro”的提示。 |
+| TAG-SORT-01 | 作为免费用户,我希望能保存标签列表顺序,以便按自己的浏览习惯组织容器。 | 免费用户拖动标签后,新的标签顺序被保存并持续生效。 |
+| TAG-SORT-02 | 作为免费用户,我希望标签列表排序不会突然弹出 Pro 提示,以便保持整理流程顺畅。 | 免费用户拖动标签结束后不出现标签排序 Pro 提示。 |
| TAG-SORT-03 | 作为 Pro 用户,我希望保存自定义标签顺序,以便长期保持自己的容器浏览习惯。 | Pro 用户拖动标签后,新的标签顺序被保存并持续生效。 |
## 13. 验收标准
@@ -357,10 +365,10 @@
| QA-TQ-10 | Pro 页功能对比展示“自定义标签 / 容器维护:免费 6 个,Pro 不限”。 |
| QA-TQ-11 | 免费用户可看到自定义容器额度计数,例如“已使用 X / 6”或“当前还可维护 X 个”。 |
| QA-TQ-12 | 触发第 7 个自定义维护动作时不创建临时标签、不保存临时改动、不进入预览,只展示“升级 Pro”和“管理已有标签”。 |
-| QA-TS-01 | 免费用户可以拖动标签,并看到排序预览效果。 |
-| QA-TS-02 | 免费用户拖动标签结束后,不保存新的标签顺序。 |
-| QA-TS-03 | 免费用户重新打开、刷新或触发标签列表重建后,标签顺序恢复为当前已保存顺序。 |
-| QA-TS-04 | 免费用户拖动标签结束后,看到与 App 图标排序预览一致风格的 Pro 提示。 |
+| QA-TS-01 | 免费用户可以拖动标签,并保存新的标签顺序。 |
+| QA-TS-02 | 免费用户拖动标签结束后,不出现标签排序 Pro 提示。 |
+| QA-TS-03 | 免费用户重新打开、刷新或触发标签列表重建后,标签顺序保持为上次保存结果。 |
+| QA-TS-04 | Pro 页免费 / Pro 对比表不再展示“自定义排序:标签在列表中的位置”。 |
| QA-TS-05 | Pro 用户拖动标签结束后,新的标签顺序被保存。 |
| QA-TS-06 | Pro 用户重新打开、刷新或触发标签列表重建后,标签顺序保持为上次保存结果。 |
| QA-TS-07 | 历史已保存的标签顺序不因本需求上线被清空。 |
@@ -375,7 +383,7 @@
| 自动删除超额标签 | 不删除、不隐藏老用户或导入数据中的超额标签。 |
| 标签图标能力 | 本需求不新增标签图标或上传图标。 |
| App 图标排序规则 | 不调整容器内部 App 图标排序的现有 Pro 预览 / 保存规则。 |
-| 新增排序视觉样式 | 不为标签排序新增一套独立提示视觉,沿用现有 Pro 提示体系。 |
+| 新增排序视觉样式 | 不为容器内 App 图标排序新增一套独立提示视觉,沿用现有 Pro 提示体系。 |
| 订阅模式 | 本需求不改变 Pro 一次性买断策略。 |
## 15. 指标建议
@@ -388,8 +396,8 @@
| 触达后 7 天购买率 | 衡量该权益对转化的真实贡献。 |
| 触达后流失率 / 卸载率 | 监控限制是否过重。 |
| 删除 / 合并标签行为 | 判断用户是否能理解并管理额度。 |
-| 标签排序预览触达率 | 衡量免费用户是否会尝试调整标签顺序。 |
-| 标签排序预览后的 Pro 点击率 | 衡量“保存标签排序”权益是否具备转化价值。 |
+| 容器内 App 图标排序预览触达率 | 衡量免费用户是否会尝试调整容器内图标顺序。 |
+| 容器内 App 图标排序预览后的 Pro 点击率 | 衡量“保存容器内 App 图标排序”权益是否具备转化价值。 |
## 16. 决策记录
diff --git a/src/Scripts/pro_custom_container_quota_qa.sh b/src/Scripts/pro_custom_container_quota_qa.sh
index 7c7225f..2c69e92 100755
--- a/src/Scripts/pro_custom_container_quota_qa.sh
+++ b/src/Scripts/pro_custom_container_quota_qa.sh
@@ -26,6 +26,25 @@
fail(message)
+def function_body(source: str, signature: str) -> str:
+ start = source.find(signature)
+ if start == -1:
+ fail(f"missing function signature: {signature}")
+ brace = source.find("{", start)
+ if brace == -1:
+ fail(f"missing function body for: {signature}")
+ depth = 0
+ for index in range(brace, len(source)):
+ char = source[index]
+ if char == "{":
+ depth += 1
+ elif char == "}":
+ depth -= 1
+ if depth == 0:
+ return source[brace + 1:index]
+ fail(f"unterminated function body for: {signature}")
+
+
pro = read("ProEntitlement.swift")
data_layer = read("DataLayer.swift")
tag_editor = read("TagEditorView.swift")
@@ -57,7 +76,9 @@
require(data_layer, "static func moveApp(path: String, from sourceTag: String, to targetTag: String, color: Int, copy: Bool) -> TagEditorMutationResult", "moveApp must return a mutation result")
require(data_layer, "TagDatabase.markCustomMaintainedIfNeeded", "membership changes must mark maintained system tags")
require(data_layer, "static func reorderTags(_ names: [String]) -> Bool", "tag order persistence must return whether it saved")
-require(data_layer, "guard ProEntitlementPolicy.isUnlocked(.persistentAppSorting) else { return false }", "tag order persistence must be Pro-gated")
+reorder_tags_body = function_body(data_layer, "static func reorderTags(_ names: [String]) -> Bool")
+if "ProEntitlementPolicy.isUnlocked(.persistentAppSorting)" in reorder_tags_body:
+ fail("tag list order persistence must be available to free users")
require(tag_editor, "onCustomContainerQuotaExceeded", "TagEditorView must expose quota-blocked callback")
require(tag_editor, "onCustomContainerQuotaStatusChanged", "TagEditorView must expose quota status refresh callback")
@@ -66,8 +87,10 @@
require(tag_editor, "guard handleMutationResult(result) else { return }", "TagEditorView must not update local state before a save succeeds")
require(tag_editor, "blockedCustomContainerQuota", "TagEditorView must surface quota blocks")
-require(content, "persistTagOrderIfAllowed()", "ContentView must route tag order save through a Pro gate")
-require(content, "pro.tagSorting.previewToast", "free tag sorting preview toast is missing")
+require(content, "persistTagOrder()", "ContentView must persist tag list order")
+persist_tag_order_body = function_body(content, "private func persistTagOrder()")
+if "proEntitlement.isUnlocked" in persist_tag_order_body or "pro.tagSorting.previewToast" in persist_tag_order_body:
+ fail("free users must be able to persist tag list order without a Pro preview toast")
require(content, "presentCustomContainerQuotaPrompt", "ContentView must show custom container quota prompt")
require(content, "customContainerQuotaAccessory", "ContentView tag editor must show quota count")
require(content, "@State private var customContainerQuotaStatus = ProEntitlementPolicy.customContainerQuotaStatus()", "ContentView quota count must be backed by SwiftUI state")
@@ -84,19 +107,19 @@
require(preferences, "onCustomContainerQuotaStatusChanged: refreshSettingsCustomContainerQuotaStatus", "Settings must refresh quota count after TagEditorView saves")
require(preferences, "settingsCustomContainerQuotaOverviewStatus = quotaStatus", "Settings scanApps must initialize quota count from the same store")
require(preferences, 'title: tr("pro.feature.maintainableTagCount")', "Pro comparison must include maintainable tag count row")
-require(preferences, 'title: tr("pro.feature.tagOrderPersistence")', "Pro comparison must include tag order row")
+if 'title: tr("pro.feature.tagOrderPersistence")' in preferences:
+ fail("Pro comparison must not list tag list order; it is available to free users")
require(preferences, 'title: tr("pro.feature.containerAppOrderPersistence")', "Pro comparison must include container app order row")
-first_three = [
+first_two = [
preferences.find('title: tr("pro.feature.maintainableTagCount")'),
- preferences.find('title: tr("pro.feature.tagOrderPersistence")'),
preferences.find('title: tr("pro.feature.containerAppOrderPersistence")'),
]
custom_color_index = preferences.find('title: tr("pro.feature.customTagColors")')
-if any(index == -1 for index in first_three) or custom_color_index == -1:
+if any(index == -1 for index in first_two) or custom_color_index == -1:
fail("could not locate Pro comparison feature rows")
-if not (first_three[0] < first_three[1] < first_three[2] < custom_color_index):
- fail("new Pro comparison rows must be the first three rows")
+if not (first_two[0] < first_two[1] < custom_color_index):
+ fail("Pro comparison rows must begin with tag quota and container app order")
required_l10n = [
"pro.feature.customContainerQuota",
@@ -106,9 +129,7 @@
"pro.customContainers.quotaUnlimitedShort",
"pro.customContainers.quotaPrompt",
"pro.customContainers.manageExisting",
- "pro.tagSorting.previewToast",
"pro.feature.maintainableTagCount",
- "pro.feature.tagOrderPersistence",
"pro.feature.containerAppOrderPersistence",
"settings.proStatus.maxSixTags",
"settings.proStatus.unlimitedTags",
@@ -139,5 +160,5 @@
if zh_hans.get("settings.proStatus.unlimitedTags") != "✅没有限制":
fail("zh-Hans unlimited tag status must include the approved leading checkmark")
-print("PASS Pro custom container quota QA: quota gate, tag-order preview, Pro table rows, and 29-language keys are wired")
+print("PASS Pro custom container quota QA: quota gate, free tag-order persistence, Pro table rows, and 29-language keys are wired")
PY
--
Gitblit v1.9.3