#!/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 = root / "Apptag"
|
|
|
def fail(message: str) -> None:
|
raise SystemExit(f"FAIL: {message}")
|
|
|
def read(name: str) -> str:
|
path = app / 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")
|
preferences = read("PreferencesView.swift")
|
content = read("ContentView.swift")
|
defaults = read("AppDefaults.swift")
|
|
require(defaults, 'static let displayMode = "gridContainer"', "new installs must default to a free display mode")
|
|
for needle in [
|
"struct ProDisplayModePreviewState: Equatable",
|
"var previewDisplayMode: String? = nil",
|
'static let premiumDisplayModes: Set<String> = ["coloredContainer", "coloredGridContainer"]',
|
"static func isPremiumDisplayMode(_ displayMode: String) -> Bool",
|
"static func canUseDisplayMode(_ displayMode: String) -> Bool",
|
"static func fallbackDisplayMode(for displayMode: String) -> String",
|
"static func effectiveDisplayMode(for storedDisplayMode: String) -> String",
|
"func startDisplayModePreview(for displayMode: String)",
|
"func stopDisplayModePreview()",
|
"ProEntitlementConfig.themePreviewDuration",
|
"var isPreviewingProAppearance: Bool",
|
]:
|
require(pro, needle, f"Pro display-mode preview contract is missing: {needle}")
|
|
fallback_body = function_body(pro, "static func fallbackDisplayMode(for displayMode: String) -> String")
|
require(fallback_body, 'case "coloredContainer":', "coloredContainer must have a free fallback")
|
require(fallback_body, 'return "container"', "coloredContainer must fall back to container")
|
require(fallback_body, 'case "coloredGridContainer":', "coloredGridContainer must have a free fallback")
|
require(fallback_body, 'return "gridContainer"', "coloredGridContainer must fall back to gridContainer")
|
|
effective_body = function_body(pro, "static func effectiveDisplayMode(for storedDisplayMode: String) -> String")
|
require(effective_body, "snapshot.previewDisplayMode", "effective display mode must prefer active preview")
|
require(effective_body, "canUseDisplayMode(storedDisplayMode)", "effective display mode must enforce Pro access")
|
require(effective_body, "fallbackDisplayMode(for: storedDisplayMode)", "locked stored display modes must fall back")
|
|
start_body = function_body(pro, "func startDisplayModePreview(for displayMode: String)")
|
require(start_body, "ProEntitlementPolicy.isPremiumDisplayMode(displayMode)", "only premium display modes should start preview")
|
require(start_body, "guard !accessState.isUnlocked else { return }", "Pro users should not start a trial preview")
|
require(start_body, "stopThemePreview()", "display-mode preview must share one active Pro appearance trial with themes")
|
require(start_body, "endsAt: now.addingTimeInterval(ProEntitlementConfig.themePreviewDuration)", "display-mode preview must reuse Pro theme 5-minute duration")
|
require(start_body, "applyDisplayModePreview(preview)", "display-mode preview must publish snapshot state")
|
|
require(preferences, "private var effectiveDisplayMode: String", "Preferences must expose effective display mode")
|
require(preferences, "private func handleDisplayModeSelection(_ mode: String)", "Preferences display mode handler is missing")
|
require(preferences, "private func displayModeOptionAccessory(for mode: String) -> ThemeOptionAccessory", "display mode accessory policy is missing")
|
require(preferences, "isSelected: effectiveDisplayMode ==", "display buttons must select by effective display mode")
|
require(preferences, "accessory: displayModeOptionAccessory", "display buttons must render Pro badge/countdown accessory")
|
require(preferences, "proEntitlement.startDisplayModePreview(for: mode)", "locked display-mode selection must start preview")
|
require(preferences, ".frame(width: 64, alignment: .trailing)", "display mode buttons must reserve a fixed accessory slot")
|
require(preferences, "ProStatusPill(text: text, style: style, compact: true)", "display mode Pro badge must use the shared Pro pill")
|
require(preferences, ".font(.system(size: 10, weight: .semibold, design: .monospaced))", "display mode countdown must use the shared MM:SS visual")
|
|
handler_body = function_body(preferences, "private func handleDisplayModeSelection(_ mode: String)")
|
require(handler_body, "ProEntitlementPolicy.canUseDisplayMode(mode)", "display mode handler must branch on Pro access")
|
require(handler_body, "displayMode = mode", "unlocked/free display modes must persist")
|
require(handler_body, "proEntitlement.startDisplayModePreview(for: mode)", "locked premium display modes must not persist immediately")
|
if handler_body.find("displayMode = mode") > handler_body.find("proEntitlement.startDisplayModePreview(for: mode)"):
|
fail("locked display mode preview must not write displayMode before starting preview")
|
|
accessory_body = function_body(preferences, "private func displayModeOptionAccessory(for mode: String) -> ThemeOptionAccessory")
|
require(accessory_body, "displayModePreviewState?.displayMode == mode", "active display mode preview must show countdown on that option")
|
require(accessory_body, ".countdown(text: themePreviewCountdownText ?? \"00:00\")", "active display mode preview must show MM:SS")
|
require(accessory_body, "ProEntitlementPolicy.isPremiumDisplayMode(mode)", "only premium display modes should show locked Pro badge")
|
require(accessory_body, "return effectiveDisplayMode == mode ? .checkmark : .circle", "ordinary selected state must use effective display mode")
|
|
countdown_body = function_body(preferences, "private var themePreviewCountdownText: String?")
|
require(countdown_body, "proEntitlement.displayModePreviewState", "shared countdown text must support display-mode previews")
|
|
refresh_body = function_body(preferences, "private func refreshThemePreviewCountdown(now: Date = Date())")
|
require(refresh_body, "displayModePreviewState", "timer refresh must expire display-mode previews")
|
require(refresh_body, "proEntitlement.stopDisplayModePreview()", "expired display-mode preview must stop")
|
|
access_body = function_body(preferences, "private func handleProAccessStateChange(_ newState: ProAccessState)")
|
require(access_body, "if let preview = proEntitlement.displayModePreviewState", "unlock flow must accept display-mode preview")
|
require(access_body, "displayMode = preview.displayMode", "unlock flow must persist accepted display-mode preview")
|
require(access_body, "proEntitlement.stopDisplayModePreview()", "unlock flow must clear display-mode preview after persisting")
|
|
require(content, "private var renderedDisplayMode: String", "ContentView must expose entitlement-aware display mode")
|
require(content, "ProEntitlementPolicy.effectiveDisplayMode(for: displayMode)", "ContentView must calculate effective display mode")
|
require(content, "displayMode: renderedDisplayMode", "AppGrid must render with effective display mode")
|
require(content, "renderedDisplayMode == \"container\" || renderedDisplayMode == \"gridContainer\"", "colorless-container behavior must use effective display mode")
|
require(content, "if let preview = proEntitlement.displayModePreviewState", "ContentView unlock flow must accept display-mode preview")
|
require(content, "displayMode = preview.displayMode", "ContentView must persist accepted display-mode preview")
|
|
if re.search(r'isSelected:\s*displayMode\s*==', preferences):
|
fail("display mode buttons still select by raw stored displayMode")
|
if re.search(r'displayMode:\s*displayMode\s*,', content):
|
fail("AppGridCollectionView still receives raw stored displayMode")
|
|
print("PASS Pro display-mode preview QA: colored container/grid are Pro-only, free users get a 5-minute non-persistent preview, UI badges/countdown are aligned, and AppGrid renders the effective display mode")
|
PY
|