From 70a1cc2e63d2c49a135bd89cec3ffb1d427d90ee Mon Sep 17 00:00:00 2001
From: Ariver <shanghai3168@gmail.com>
Date: Tue, 30 Jun 2026 16:21:47 +0800
Subject: [PATCH] Freeze prep TagLauncher 8.2.9 Pro status UI

---
 src/Apptag/DataLayer.swift |  110 +++++++++++++++++++++++++++++++++++++++++++++++++++---
 1 files changed, 103 insertions(+), 7 deletions(-)

diff --git a/src/Apptag/DataLayer.swift b/src/Apptag/DataLayer.swift
index d05beda..2a23bbd 100644
--- a/src/Apptag/DataLayer.swift
+++ b/src/Apptag/DataLayer.swift
@@ -237,6 +237,52 @@
 
 // MARK: - Tag Color Mapping
 
+struct TagCustomColor: Codable, Equatable {
+    var red: Double
+    var green: Double
+    var blue: Double
+    var alpha: Double
+
+    init(red: Double, green: Double, blue: Double, alpha: Double = 1.0) {
+        self.red = Self.clamp(red)
+        self.green = Self.clamp(green)
+        self.blue = Self.clamp(blue)
+        self.alpha = Self.clamp(alpha)
+    }
+
+    init(nsColor: NSColor) {
+        let rgbColor = nsColor.usingColorSpace(.sRGB) ?? nsColor
+        self.init(
+            red: Double(rgbColor.redComponent),
+            green: Double(rgbColor.greenComponent),
+            blue: Double(rgbColor.blueComponent),
+            alpha: Double(rgbColor.alphaComponent)
+        )
+    }
+
+    var nsColor: NSColor {
+        NSColor(
+            srgbRed: CGFloat(red),
+            green: CGFloat(green),
+            blue: CGFloat(blue),
+            alpha: CGFloat(alpha)
+        )
+    }
+
+    var signature: String {
+        [
+            String(format: "%.4f", red),
+            String(format: "%.4f", green),
+            String(format: "%.4f", blue),
+            String(format: "%.4f", alpha)
+        ].joined(separator: ":")
+    }
+
+    private static func clamp(_ value: Double) -> Double {
+        min(1.0, max(0.0, value))
+    }
+}
+
 enum TagColor {
     static func nsColor(for index: Int) -> NSColor {
         switch index {
@@ -251,6 +297,20 @@
         default: return NSColor.systemGray
         }
     }
+
+    static func nsColor(for index: Int, customColor: TagCustomColor?) -> NSColor {
+        customColor?.nsColor ?? nsColor(for: index)
+    }
+
+    static func prefersDarkText(for color: NSColor) -> Bool {
+        let rgbColor = color.usingColorSpace(.sRGB) ?? color
+        let red = Double(rgbColor.redComponent)
+        let green = Double(rgbColor.greenComponent)
+        let blue = Double(rgbColor.blueComponent)
+        let luminance = (0.299 * red) + (0.587 * green) + (0.114 * blue)
+        return luminance > 0.62
+    }
+
     static let allIndices: [Int] = [1, 2, 3, 4, 5, 6, 7]
 }
 
@@ -963,6 +1023,7 @@
     struct TagDef: Codable, Equatable {
         var color: Int
         var systemCategoryID: SmartCategoryID? = nil
+        var customColor: TagCustomColor? = nil
     }
 
     struct Store: Codable {
@@ -1551,12 +1612,14 @@
     // MARK: Export / Import
 
     static func exportTo(_ url: URL) throws {
+        try ProEntitlementPolicy.requireUnlocked(.layoutExport)
         let store = loadWithEnsuredCategoryScheme()
         let data = try JSONEncoder().encode(store)
         try data.write(to: url, options: .atomic)
     }
 
     static func importFrom(_ url: URL) throws -> Store {
+        try ProEntitlementPolicy.requireUnlocked(.layoutImport)
         let previousStore = loadWithEnsuredCategoryScheme()
         let data = try Data(contentsOf: url)
         var store = try JSONDecoder().decode(Store.self, from: data)
@@ -1753,12 +1816,11 @@
         guard !FileManager.default.fileExists(atPath: storeURL.path) else { return }
 
         let starterCategoryIDs: [SmartCategoryID] = [
-            .uiPrototyping,
-            .ide,
-            .writing,
-            .game,
+            .design,
+            .development,
+            .office,
             .entertainment,
-            .system,
+            .systemEnhancement,
             .gtd
         ]
 
@@ -1808,6 +1870,8 @@
 
     /// Persist the visible app order for one stable container.
     static func reorderApps(inContainer containerID: String, orderedPaths: [String]) {
+        guard ProEntitlementPolicy.isUnlocked(.persistentAppSorting) else { return }
+
         var store = TagDatabase.load()
         _ = TagDatabase.normalizeContainerAppOrder(in: &store)
         let previousStore = store
@@ -2137,11 +2201,22 @@
         TagDatabase.save(store)
     }
 
-    static func setAppNote(_ note: String, for path: String) {
+    @discardableResult
+    static func setAppNote(_ note: String, for path: String) -> ProNoteSaveDecision {
         var store = TagDatabase.load()
-        let previousStore = store
         let trimmed = note.trimmingCharacters(in: .whitespacesAndNewlines)
         let limited = String(trimmed.prefix(TagDatabase.maxAppNoteLength))
+        let decision = ProEntitlementPolicy.noteSaveDecision(
+            note: limited,
+            for: path,
+            in: store
+        )
+
+        guard case .allow = decision else {
+            return decision
+        }
+
+        let previousStore = store
         if limited.isEmpty {
             store.appNotes.removeValue(forKey: path)
             store.appNoteMetadata[path] = TagDatabase.AppNoteMetadata(
@@ -2167,6 +2242,7 @@
             previous: previousStore,
             reason: "edit-app-note"
         )
+        return decision
     }
 
     static func moveApp(path: String, from sourceTag: String, to targetTag: String, color: Int, copy: Bool) {
@@ -2272,6 +2348,7 @@
         let previousStore = store
         var tagDef = store.tags[tag] ?? TagDatabase.TagDef(color: color, systemCategoryID: nil)
         tagDef.color = color
+        tagDef.customColor = nil
         store.tags[tag] = tagDef
         TagDatabase.saveUserCategorySchemeMutation(
             store,
@@ -2279,4 +2356,23 @@
             reason: "set-tag-color"
         )
     }
+
+    /// Set a Pro custom color for a tag without changing its fallback base color.
+    @discardableResult
+    static func setCustomColor(_ customColor: TagCustomColor, for tag: String) -> Bool {
+        guard ProEntitlementPolicy.isUnlocked(.customTagColors) else {
+            return false
+        }
+        var store = TagDatabase.load()
+        let previousStore = store
+        var tagDef = store.tags[tag] ?? TagDatabase.TagDef(color: 0, systemCategoryID: nil)
+        tagDef.customColor = customColor
+        store.tags[tag] = tagDef
+        TagDatabase.saveUserCategorySchemeMutation(
+            store,
+            previous: previousStore,
+            reason: "set-tag-custom-color"
+        )
+        return true
+    }
 }

--
Gitblit v1.9.3