Ariver
2026-08-22 eab3bec52af5db03d411a5e079895b3659ead524
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
#!/usr/bin/env bash
set -euo pipefail
 
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
 
python3 - "$ROOT_DIR" <<'PY'
import re
import sys
from pathlib import Path
 
root = Path(sys.argv[1])
app_dir = root / "Apptag"
 
 
def fail(message: str) -> None:
    raise SystemExit(f"FAIL: {message}")
 
 
def read(name: str) -> str:
    path = app_dir / name
    try:
        return path.read_text(encoding="utf-8")
    except Exception as error:
        fail(f"could not read {path}: {error}")
 
 
def require(source: str, needle: str, message: str) -> None:
    if needle not in source:
        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")
app = read("ApptagApp.swift")
theme = read("AppGridTheme.swift")
preferences = read("PreferencesView.swift")
content = read("ContentView.swift")
access_views = read("ProAccessViews.swift")
 
for case in [
    "premiumThemes",
    "layoutImport",
    "layoutExport",
    "customTagColors",
    "customContainerQuota",
    "unlimitedNotes",
    "persistentAppSorting",
    "customHotkeys",
]:
    require(pro, f"case {case}", f"missing ProFeature.{case}")
 
require(pro, 'static let lifetimeProductID = "com.taglauncher.pro.lifetime"', "StoreKit lifetime product id changed or missing")
require(pro, 'static let firstFreeProVersion = "8.3.2"', "legacy unlock cutoff must protect paid downloads before 8.3.2")
require(pro, 'static let qaStateEnvKey = "TAGLAUNCHER_QA_PRO_STATE"', "QA Pro-state override is missing")
require(pro, "static let freeNoteLimit = 3", "free note limit must be 3")
require(pro, "static let freeThemes: Set<AppGridTheme> = [.defaultLight, .black]", "free theme set must be default/black")
require(pro, 'static let premiumDisplayModes: Set<String> = ["coloredContainer", "coloredGridContainer"]', "colored container/grid display modes must be Pro-only")
require(pro, "static func effectiveDisplayMode(for storedDisplayMode: String) -> String", "effective display mode gate is missing")
require(pro, "func startDisplayModePreview(for displayMode: String)", "Pro display-mode trial preview is missing")
require(pro, "Product.products(for: [ProEntitlementConfig.lifetimeProductID])", "StoreKit product lookup is missing")
require(pro, "Transaction.currentEntitlements", "current entitlement restore/read path is missing")
require(pro, "Transaction.updates", "transaction update listener is missing")
require(pro, "AppTransaction.shared", "legacy original app version path is missing")
require(pro, "appTransaction.originalAppVersion", "legacy originalAppVersion check is missing")
require(pro, "appTransaction.originalPurchaseDate", "legacy originalPurchaseDate fallback is missing")
require(pro, "SemanticVersion(ProEntitlementConfig.firstFreeProVersion)", "legacy semantic version cutoff is missing")
require(pro, "ProEntitlementSnapshotStore", "sync entitlement snapshot for data-layer policy is missing")
require(pro, "manualNonEmptyNoteCount", "note quota must count existing manual notes")
require(pro, "manualNonEmptyNotePaths", "note quota must avoid double-counting existing notes")
require(pro, "case .premiumThemes, .layoutImport, .layoutExport, .customTagColors, .customContainerQuota, .unlimitedNotes, .persistentAppSorting, .customHotkeys:", "custom tag colors, custom container quota, and custom hotkeys must be unlocked only by the Pro access state")
require(pro, "NotificationCenter.default.post(name: .tagLauncherProEntitlementChanged", "Pro entitlement changes must notify hotkey registration")
 
effective_theme = function_body(pro, "static func effectiveTheme(for storedTheme: AppGridTheme) -> AppGridTheme")
require(effective_theme, "return .defaultLight", "locked premium theme must fall back to defaultLight")
require(effective_theme, "canUseTheme(storedTheme)", "effectiveTheme must check Pro theme entitlement")
 
effective_display_mode = function_body(pro, "static func effectiveDisplayMode(for storedDisplayMode: String) -> String")
require(effective_display_mode, "snapshot.previewDisplayMode", "effective display mode must prefer active preview")
require(effective_display_mode, "canUseDisplayMode(storedDisplayMode)", "effective display mode must check Pro entitlement")
require(effective_display_mode, "fallbackDisplayMode(for: storedDisplayMode)", "locked colored display mode must fall back to its free variant")
 
require(app, "ProEntitlementCenter.shared.start()", "App startup does not start Pro entitlement center")
require(app, "observeProEntitlementChanges()", "App startup must observe Pro entitlement changes for custom hotkeys")
require(app, "applyCustomHotkey(_ hotkey: LauncherHotkey, for kind: LauncherHotkeyKind)", "AppDelegate custom hotkey save entry is missing")
require(app, "guard ProEntitlementPolicy.isUnlocked(.customHotkeys) else", "custom hotkey save/restore must be hard-gated by Pro")
require(app, "LauncherHotkeySettings.effectiveHotkey(for: kind)", "global hotkey registration must use the entitlement-aware effective shortcut")
 
require(theme, "var requiresPro: Bool", "AppGridTheme does not expose Pro requirement")
require(theme, "ProEntitlementConfig.freeThemes.contains(self)", "theme Pro requirement must use central free theme set")
require(theme, "static var premiumThemes: [AppGridTheme]", "theme premium list is missing")
 
export_body = function_body(data_layer, "static func exportTo(_ url: URL) throws")
require(export_body, "try ProEntitlementPolicy.requireUnlocked(.layoutExport)", "layout export is not locked at data layer")
if export_body.find("try ProEntitlementPolicy.requireUnlocked(.layoutExport)") > export_body.find("data.write"):
    fail("layout export lock must run before writing data")
 
import_body = function_body(data_layer, "static func importFrom(_ url: URL) throws -> Store")
require(import_body, "try ProEntitlementPolicy.requireUnlocked(.layoutImport)", "layout import is not locked at data layer")
if import_body.find("try ProEntitlementPolicy.requireUnlocked(.layoutImport)") > import_body.find("Data(contentsOf: url)"):
    fail("layout import lock must run before reading user-selected file")
 
reorder_body = function_body(data_layer, "static func reorderApps(inContainer containerID: String, orderedPaths: [String])")
require(reorder_body, "guard ProEntitlementPolicy.isUnlocked(.persistentAppSorting) else { return }", "app sorting persistence is not locked")
if reorder_body.find("guard ProEntitlementPolicy.isUnlocked(.persistentAppSorting) else { return }") > reorder_body.find("var store = TagDatabase.load()"):
    fail("app sorting lock must run before loading/writing store")
 
note_body = function_body(data_layer, "static func setAppNote(_ note: String, for path: String) -> ProNoteSaveDecision")
require(note_body, "ProEntitlementPolicy.noteSaveDecision", "note save does not check Pro quota")
require(note_body, "guard case .allow = decision else", "blocked note save must return without mutating store")
if note_body.find("guard case .allow = decision else") > note_body.find("let previousStore = store"):
    fail("note quota decision must happen before store mutation snapshot")
 
require(data_layer, "var customColor: TagCustomColor? = nil", "tag definitions must store an optional custom color")
require(data_layer, "struct TagCustomColor: Codable, Equatable", "custom tag color must be Codable and comparable")
require(data_layer, "static func nsColor(for index: Int, customColor: TagCustomColor?) -> NSColor", "tag rendering must prefer custom colors with base-color fallback")
set_color_body = function_body(data_layer, "static func setColor(_ color: Int, for tag: String)")
require(set_color_body, "tagDef.customColor = nil", "selecting a base tag color must clear the custom color")
set_custom_color_body = function_body(data_layer, "static func setCustomColor(_ customColor: TagCustomColor, for tag: String) -> Bool")
require(set_custom_color_body, "guard ProEntitlementPolicy.isUnlocked(.customTagColors) else", "custom tag color writes must be hard-gated by Pro")
if set_custom_color_body.find("guard ProEntitlementPolicy.isUnlocked(.customTagColors) else") > set_custom_color_body.find("var store = TagDatabase.load()"):
    fail("custom tag color lock must run before loading/writing store")
 
export_ui = function_body(preferences, "private func exportTags()")
require(export_ui, "guard proEntitlement.isUnlocked else", "export UI must prompt Pro before file panel")
require(export_ui, "presentSettingsProPrompt(for: .layoutExport)", "export UI prompt feature is missing")
if export_ui.find("guard proEntitlement.isUnlocked else") > export_ui.find("prepareDataFilePanelPresentation()"):
    fail("export UI must check Pro before opening file panel")
 
import_ui = function_body(preferences, "private func importTags()")
require(import_ui, "guard proEntitlement.isUnlocked else", "import UI must prompt Pro before file panel")
require(import_ui, "presentSettingsProPrompt(for: .layoutImport)", "import UI prompt feature is missing")
if import_ui.find("guard proEntitlement.isUnlocked else") > import_ui.find("prepareDataFilePanelPresentation()"):
    fail("import UI must check Pro before opening file panel")
 
require(preferences, "ProStatusPill(text: tr(\"pro.card.badge\"), style: .locked, compact: true)", "locked data actions must show compact Pro pill")
require(preferences, "presentSettingsProPrompt(for: .premiumThemes)", "premium theme selection prompt is missing")
require(preferences, "presentSettingsProPrompt(for: .customHotkeys)", "custom hotkey Pro prompt is missing")
require(preferences, "presentSettingsProPrompt(for: .customTagColors)", "custom tag color Pro prompt is missing")
require(preferences, "private var settingsFixedProHeader: some View", "Settings pages must share one fixed-position Pro guidance header")
require(preferences, "private var settingsProHeaderText: String", "fixed Pro guidance header must switch copy by selected tab")
require(preferences, "private var settingsProHeaderFeature: ProFeature", "fixed Pro guidance header must route unlock prompts by selected tab")
require(preferences, "private var shouldCenterUnlockedIdentityHeader: Bool", "Language/Pro/About tabs need the centered unlocked Pro identity header")
center_identity_body = function_body(preferences, "private var shouldCenterUnlockedIdentityHeader: Bool")
require(center_identity_body, "guard proEntitlement.isUnlocked else { return false }", "centered Pro identity header must only apply after Pro unlock")
require(center_identity_body, "case .language, .pro, .about:", "centered Pro identity header must apply to Language, Pro, and About tabs")
require(center_identity_body, "case .general, .theme, .hotkeys, .tags, .data:", "centered Pro identity header must not affect the feature-specific settings tabs")
settings_header_text_body = function_body(preferences, "private var settingsProHeaderText: String")
require(settings_header_text_body, "case .language, .about:", "Language/About tabs must have an explicit fixed-header text branch")
require(settings_header_text_body, 'return ""', "Language/About tabs must not show the general Pro guidance sentence")
if "case .general, .language, .about:" in settings_header_text_body:
    fail("Language/About tabs must not reuse the General tab Pro guidance sentence")
require(preferences, "private func proHeaderStatusLabel(compact: Bool = false) -> some View", "fixed Pro guidance header must render the free status as plain text")
require(preferences, 'Text(tr("settings.proStatus.freeUser"))', "Free status must render as the full current-user sentence instead of a Free pill")
require(preferences, "private var shouldShowSettingsFixedProHeader: Bool", "all settings tabs must use the fixed Pro header gate")
require(function_body(preferences, "private var shouldShowSettingsFixedProHeader: Bool"), "true", "fixed Pro header must be shown on Language and About tabs too")
if "selectedTab != .language && selectedTab != .about" in preferences:
    fail("fixed Pro header must no longer be hidden on Language and About tabs")
require(preferences, "if shouldShowSettingsFixedProHeader", "Settings body must conditionally render the fixed Pro header")
fixed_header_body = function_body(preferences, "private var settingsFixedProHeader: some View")
if "Color(nsColor: .controlBackgroundColor)" in fixed_header_body:
    fail("fixed Pro header must not render the removed outer rounded background")
if ".stroke(Color.secondary.opacity(0.16), lineWidth: 1)" in fixed_header_body:
    fail("fixed Pro header must not render the removed outer rounded border")
require(fixed_header_body, "if shouldCenterUnlockedIdentityHeader", "fixed Pro header must use the centered unlocked identity branch")
require(fixed_header_body, 'text: tr("pro.status.unlocked")', "centered Pro identity header must start with the Pro unlocked pill")
require(fixed_header_body, "style: .unlocked", "centered Pro identity pill must use the unlocked style")
require(fixed_header_body, 'Text(tr("settings.proStatus.proUser"))', "centered Pro identity header must render the current Pro user sentence")
if fixed_header_body.find("if shouldCenterUnlockedIdentityHeader") > fixed_header_body.find('Text(tr("settings.proStatus.proUser"))'):
    fail("centered Pro identity sentence must be inside the centered branch")
if fixed_header_body.count("Spacer(minLength: 0)") < 2:
    fail("centered Pro identity header must be horizontally centered with leading and trailing spacers")
require(fixed_header_body, "if !proEntitlement.isUnlocked", "fixed Pro header must branch on unlocked state")
require(fixed_header_body, 'Button(tr("pro.card.restore"))', "fixed Pro header must keep restore purchase for free users")
if fixed_header_body.find("if !proEntitlement.isUnlocked") > fixed_header_body.find('Button(tr("pro.card.restore"))'):
    fail("fixed Pro header restore purchase button must only render for users who have not unlocked Pro")
purchase_guidance_body = function_body(preferences, "private func proPurchaseGuidanceLine(")
require(purchase_guidance_body, "if !proEntitlement.isUnlocked", "purchase guidance restore actions must be hidden after Pro unlock")
for unlocked_benefit_key in [
    "settings.proBenefit.general",
    "pro.theme.status.unlocked",
    "settings.proBenefit.hotkeys",
    "settings.proBenefit.tags",
    "settings.proBenefit.data",
]:
    require(preferences, unlocked_benefit_key, f"missing unlocked Pro benefit line for {unlocked_benefit_key}")
for purchase_guidance_key in [
    "settings.proGuide.general",
    "settings.proGuide.hotkeys",
    "settings.proGuide.tags",
    "settings.proGuide.data",
]:
    require(preferences, purchase_guidance_key, f"missing Pro purchase guidance line for {purchase_guidance_key}")
require(preferences, 'freeStatusKey: "settings.proStatus.unavailableFeature"', "Pro comparison locked rows must show unavailable for Free users")
require(preferences, 'freeStatusKey: "settings.proStatus.previewFiveMinutes"', "Pro comparison preview rows must show the 5-minute preview state for Free users")
require(preferences, 'proLockedStatusKey: "settings.proStatus.lockedFeature"', "Pro comparison Pro column must show supported before purchase")
require(preferences, 'proUnlockedStatusKey: "settings.proStatus.unlockedFeature"', "Pro comparison Pro column must show unlocked after purchase")
if preferences.count("freeStyle: .neutral") < 6 or preferences.count("proLockedStyle: .neutral") < 6 or preferences.count("proUnlockedStyle: .neutral") < 6:
    fail("Pro comparison status chips must use the neutral style without crown icons")
if 'freeStatusKey: "settings.proStatus.limitedFeature"' in preferences or 'freeStatusKey: "settings.proStatus.unsupportedFeature"' in preferences:
    fail("Pro comparison table must not use the old Limited/Unsupported status for locked rows")
if "settingsProStatusOverview(" in preferences or "SettingsProOverviewFeature" in preferences:
    fail("settings pages must not repeat the bulky Pro status overview card across tabs")
for removed_about_pro_ui in [
    "proStatusCard",
    "settingsProComparisonTable",
    "SettingsProComparisonFeature",
]:
    if removed_about_pro_ui in preferences:
        fail(f"about page must not render the removed Pro summary/comparison UI: {removed_about_pro_ui}")
require(preferences, "case pro", "Settings must include a dedicated Pro tab")
require(preferences, 'case .pro: return "pro.card.badge"', "Pro tab title must use the existing Pro label")
require(preferences, 'case .pro: return "crown"', "Pro tab must use the line-style crown icon")
for filled_tab_icon in ["paintpalette.fill", "tag.fill", "externaldrive.fill"]:
    if filled_tab_icon in preferences:
        fail(f"settings tab icons must use line style, found filled icon: {filled_tab_icon}")
require(preferences, "HStack(spacing: 6)", "settings tab bar spacing must be tightened for 8 tabs")
require(preferences, ".frame(width: 94, height: 72)", "settings tab buttons must be tightened for 8 tabs")
require(preferences, "case .pro:", "Settings tab switch must render the Pro tab")
require(content, 'static let pro = "pro"', "Settings tab target must include the Pro tab")
require(app, "openPreferences(targetTab: SettingsTabTarget.pro)", "status menu Pro CTA must open the Pro settings tab")
require(preferences, 'selectedTab == .pro', "fixed Pro header must render the Pro tab user status state")
require(preferences, "proTabComparisonTable(width: dataPanelWidth)", "Pro tab must render the Free-vs-Pro comparison table")
require(preferences, "private struct ProTabComparisonFeature", "Pro tab comparison row model is missing")
comparison_body = function_body(preferences, "private func proTabComparisonTable(width: CGFloat)")
if comparison_body.count(".frame(width: 118, alignment: .leading)") < 4:
    fail("Pro comparison Free/Pro columns and status chips must be left-aligned")
if ".frame(width: 118, alignment: .center)" in comparison_body:
    fail("Pro comparison status columns must not be centered")
if 'Text(tr("settings.proCompare.title"))' in preferences:
    fail("Pro tab comparison table must not render the removed title")
require(preferences, 'Text(tr("settings.proCompare.free"))', "Pro tab comparison table free column is missing")
require(preferences, 'Text(tr("settings.proCompare.pro"))', "Pro tab comparison table Pro column is missing")
require(preferences, 'Image(systemName: "crown")', "Pro tab Pro column header must show a crown icon before the text")
if 'Text(tr("settings.language"))' in preferences:
    fail("Language tab must not render the removed header/title block")
require(preferences, "centeredSettingsScrollContent<Content: View>", "settings pages with sparse content must use the shared global centering container")
require(preferences, "private let languageContentWidth: CGFloat = 820", "language picker matrix must have its own centered width aligned with fixed settings panels")
require(preferences, "centeredSettingsScrollContent(width: languageContentWidth)", "language picker matrix must use the centered settings container with the language-specific width")
if preferences.count("centeredSettingsScrollContent(width: dataPanelWidth)") < 2:
    fail("Pro and About pages must both use the shared centered settings container")
require(preferences, ".frame(minHeight: proxy.size.height, alignment: .center)", "centered settings content must be vertically centered in the visible area")
if "topLeadingAccessory: AnyView(settingsProStatusOverview" in preferences:
    fail("Tags page must not render a second bulky Pro guidance row inside the tag editor")
if preferences.count("ScrollView(.vertical, showsIndicators: true)") < 3:
    fail("hotkeys/data/about pages must use vertical scroll containers to avoid clipping")
require(preferences, "NSEvent.addLocalMonitorForEvents(matching: .keyDown)", "custom hotkey recording must be local to Settings")
require(preferences, "settingsEscapeMonitor", "Settings must own a local Escape monitor for overlay presentation")
require(preferences, "installSettingsEscapeMonitor()", "Settings Escape monitor must be installed when Preferences appears")
require(preferences, "removeSettingsEscapeMonitor()", "Settings Escape monitor must be removed when Preferences disappears")
settings_escape_body = function_body(preferences, "private func handleSettingsEscapeKey() -> Bool")
for needle, message in [
    ("guard !isDataFilePanelPresented, window.attachedSheet == nil else { return false }", "Settings Escape must let file panels/sheets handle Escape first"),
    ("dismissSettingsProPrompt()", "Settings Escape must close the in-window Pro prompt before closing Settings"),
    ("showApplySystemSchemeConfirmation = false", "Settings Escape must close apply-confirmation overlays before closing Settings"),
    ("showResetToUncategorizedConfirmation = false", "Settings Escape must close reset-confirmation overlays before closing Settings"),
    ("stopHotkeyRecording(showCancelledToast: true)", "Settings Escape must cancel hotkey recording before closing Settings"),
    ("window.performClose(nil)", "Settings Escape must close the Settings window in the normal overlay case"),
]:
    require(settings_escape_body, needle, message)
settings_escape_filter_body = function_body(preferences, "private func isSettingsOverlayEscapeEvent(_ event: NSEvent) -> Bool")
for needle, message in [
    ("let parent = window.parent as? OverlayPanel", "Settings Escape must be limited to Settings shown over the AppGrid overlay"),
    ("parent.isVisible", "Settings Escape must require the overlay parent to be visible"),
    ("eventWindow == window || eventWindow == parent", "Settings Escape must handle both Settings-key and overlay-key event routing"),
]:
    require(settings_escape_filter_body, needle, message)
require(preferences, "proEntitlement.startThemePreview(for: theme, tuning:", "premium theme free preview is missing")
require(preferences, "proEntitlement.stopThemePreview()", "theme preview cleanup is missing")
require(preferences, "proEntitlement.startDisplayModePreview(for: mode)", "colored display mode free preview is missing")
require(preferences, "displayModeOptionAccessory(for mode: String)", "colored display mode Pro badge/countdown accessory is missing")
require(preferences, "isSelected: effectiveDisplayMode ==", "display mode buttons must select by effective display mode")
require(preferences, ".frame(width: 64, alignment: .trailing)", "display mode buttons must reserve a fixed accessory slot")
require(pro, "func remainingSeconds(at date: Date = Date()) -> Int", "theme preview remaining-seconds helper is missing")
require(pro, "ceil(endsAt.timeIntervalSince(date))", "theme preview countdown must derive from endsAt")
require(preferences, "pro.theme.status.previewCountdown", "theme preview status countdown localization is missing")
require(preferences, "formatThemePreviewCountdown(seconds:", "theme preview countdown formatter is missing")
require(preferences, ".countdown(text: themePreviewCountdownText ?? \"00:00\")", "current preview theme card must show countdown")
require(preferences, ".onReceive(themePreviewCountdownTimer)", "theme preview countdown UI timer is missing")
require(preferences, ".font(.system(size: 10, weight: .semibold, design: .monospaced))", "theme preview card countdown must use monospaced digits")
 
require(content, "@ObservedObject private var proEntitlement = ProEntitlementCenter.shared", "ContentView does not observe Pro entitlement")
require(content, "ProEntitlementPolicy.effectiveTheme(for: appGridTheme)", "ContentView does not apply effective Pro theme")
require(content, "ProEntitlementPolicy.effectiveDisplayMode(for: displayMode)", "ContentView does not apply effective Pro display mode")
require(content, "displayMode: renderedDisplayMode", "AppGrid does not render the effective display mode")
require(content, "presentProPrompt(for: .unlimitedNotes", "note quota prompt is missing from AppGrid")
require(content, "tr(\"pro.sorting.previewToast\")", "free sorting preview toast is missing")
require(content, "if let preview = proEntitlement.themePreviewState", "theme preview acceptance path is missing")
require(content, "ProUpgradePromptView(", "AppGrid Pro upgrade prompt view is missing")
 
require(access_views, "struct ProStatusPill", "ProStatusPill view is missing")
require(access_views, "struct ProUpgradePromptView", "ProUpgradePromptView is missing")
require(access_views, "let onUnlock: () -> Void", "Pro upgrade prompt purchase callback is missing")
require(access_views, "let onRestore: () -> Void", "Pro upgrade prompt restore callback is missing")
if "localizedCaseInsensitiveContains" in access_views:
    fail("ProStatusPill must not hide the crown based on localized text content")
require(content, "onUnlock: { proEntitlement.purchasePro() }", "AppGrid Pro prompt is not wired to purchase action")
require(content, "onRestore: { handleProPromptSecondaryAction(prompt) }", "AppGrid Pro prompt is not wired to secondary action handler")
require(content, "proEntitlement.restorePurchases()", "AppGrid Pro prompt secondary handler must still support restore purchase")
 
tag_editor = read("TagEditorView.swift")
require(tag_editor, "ColorPicker(", "Pro custom tag colors must use a native macOS color picker path")
require(tag_editor, "onLockedCustomColor", "locked custom color entry must route to the Pro prompt")
require(tag_editor, "TagEditor.setCustomColor", "tag editor must persist Pro custom colors")
require(tag_editor, "tagCustomColors.removeValue(forKey: tagName)", "base color selection must remove existing custom color")
require(content, "tagCustomColors = snapshot.tagDefinitions.compactMapValues { $0.customColor }", "AppGrid snapshots must propagate custom tag colors")
require(content, "customColor: tagCustomColors[$0.name]", "tag navigation must receive custom tag colors")
app_grid = read("AppGridCollectionView.swift")
tag_nav = read("TagNavigationView.swift")
require(app_grid, "let tagCustomColors: [String: TagCustomColor]", "AppGrid must accept custom tag colors")
require(app_grid, "func tagColor(for groupName: String) -> NSColor", "AppGrid must resolve custom tag color per group")
require(tag_nav, "let customColor: TagCustomColor?", "Tag navigation items must carry custom colors")
require(tag_nav, "TagColor.nsColor(for: item.colorIndex, customColor: item.customColor)", "Tag navigation must render custom colors")
 
print("PASS Pro feature gate QA: StoreKit entitlement, legacy unlock, theme gating, import/export locks, 3-note quota, custom tag colors, custom container quota, custom hotkeys, sorting preview, and Pro UI prompts are wired")
PY