#!/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: return (app_dir / name).read_text(encoding="utf-8") 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) 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") content = read("ContentView.swift") english = (app_dir / "Localization" / "en.json").read_text(encoding="utf-8") chinese = (app_dir / "Localization" / "zh-Hans.json").read_text(encoding="utf-8") require(pro, "static let freeNoteLimit = 3", "free note limit must be 3") decision_body = function_body(pro, "static func noteSaveDecision(note: String, for path: String, in store: TagDatabase.Store? = nil) -> ProNoteSaveDecision") require(decision_body, "manualNonEmptyNotePaths", "quota must count manual non-empty notes") require(decision_body, "let hasExistingManualNote = manualNotePaths.contains(path)", "editing an existing note must not consume new quota") require(decision_body, "guard manualNotePaths.count < limit else", "new notes beyond quota must be blocked") require(decision_body, "case (true, false):", "deleting an existing note must release quota") set_note_body = function_body(data_layer, "static func setAppNote(_ note: String, for path: String) -> ProNoteSaveDecision") require(set_note_body, "ProEntitlementPolicy.noteSaveDecision", "note writes must ask Pro policy") require(set_note_body, "guard case .allow = decision else", "blocked note writes must return before mutation") require(set_note_body, "store.appNotes.removeValue(forKey: path)", "empty note must delete saved note") require(set_note_body, "store.appNotes[path] = limited", "allowed non-empty note must save") require(content, "presentProPrompt(for: .unlimitedNotes, noteQuotaStatus: status)", "blocked note save must show Pro prompt") require(content, "noteQuotaHintText(for: context.app)", "editing bubble must show quota hint") require(english, "3 app notes", "English UI must say 3 app notes") require(chinese, "3 个应用备注", "Simplified Chinese UI must say 3 app notes") if re.search(r"5[- ]?(note|app notes)|5 个应用备注|5 条备注", english + chinese): fail("old 5-note copy remains in English or Simplified Chinese") print("PASS Pro notes quota QA: free limit is 3, existing edits/deletes are handled, over-quota writes are blocked, and UI copy no longer says 5 notes") PY