From 2774b3ea8fbcc13dc69bbd2cde9e641eeb105679 Mon Sep 17 00:00:00 2001
From: Ariver <shanghai3168@gmail.com>
Date: Thu, 11 Jun 2026 19:06:33 +0800
Subject: [PATCH] Add uncategorized layout reset option

---
 src/Apptag/DataLayer.swift |   43 +++++++++++++++++++++++++++++++++++++------
 1 files changed, 37 insertions(+), 6 deletions(-)

diff --git a/src/Apptag/DataLayer.swift b/src/Apptag/DataLayer.swift
index d6cd436..1cc92f4 100644
--- a/src/Apptag/DataLayer.swift
+++ b/src/Apptag/DataLayer.swift
@@ -334,7 +334,7 @@
             }
         }
 
-        return deduplicated(apps).sorted {
+        return deduplicated(apps.filter { !isInternalHelperAppPath($0.path) }).sorted {
             $0.name.localizedStandardCompare($1.name) == .orderedAscending
         }
     }
@@ -363,10 +363,10 @@
         guard url.pathExtension.lowercased() == "app" else { return }
 
         let displayURL = url.standardizedFileURL
-        guard !isNestedInsideAppBundle(displayURL) else { return }
+        guard !isInternalHelperAppPath(displayURL) else { return }
 
         let resolvedURL = displayURL.resolvingSymlinksInPath().standardizedFileURL
-        guard !isNestedInsideAppBundle(resolvedURL) else { return }
+        guard !isInternalHelperAppPath(resolvedURL) else { return }
         guard seenResolvedPaths.insert(resolvedURL.path).inserted else { return }
 
         let bundle = Bundle(url: displayURL) ?? Bundle(url: resolvedURL)
@@ -624,11 +624,29 @@
     }
 
     static func isNestedInsideAppBundle(_ url: URL) -> Bool {
-        let components = url.standardizedFileURL.pathComponents
-        guard let lastAppIndex = components.lastIndex(where: { $0.lowercased().hasSuffix(".app") }) else {
+        isNestedInsideAppBundlePath(url.standardizedFileURL.path)
+    }
+
+    static func isInternalHelperAppPath(_ url: URL) -> Bool {
+        let path = url.standardizedFileURL.path
+        let lowercasedPath = path.lowercased()
+        return isNestedInsideAppBundlePath(path)
+            || lowercasedPath.contains("/contents/helpers/")
+            || lowercasedPath.contains("/contents/xpcservices/")
+            || lowercasedPath.contains("/wrapper/")
+    }
+
+    private static func isNestedInsideAppBundlePath(_ path: String) -> Bool {
+        let lowercasedPath = path.lowercased()
+        guard lowercasedPath.hasSuffix(".app") else { return false }
+        let components = lowercasedPath.split(separator: "/", omittingEmptySubsequences: true)
+        guard let lastAppIndex = components.lastIndex(where: { $0.hasSuffix(".app") }) else {
             return false
         }
-        return components[..<lastAppIndex].contains { $0.lowercased().hasSuffix(".app") }
+        if components[..<lastAppIndex].contains(where: { $0.hasSuffix(".app") }) {
+            return true
+        }
+        return lowercasedPath.range(of: ".app/", options: .caseInsensitive) != nil
     }
 
     private static func deduplicated(_ apps: [AppInfo]) -> [AppInfo] {
@@ -1216,6 +1234,19 @@
         return store
     }
 
+    @discardableResult
+    static func resetAppTagAssignmentsToUncategorized() -> Store {
+        let previousStore = loadWithEnsuredCategoryScheme()
+        var store = previousStore
+        store.appTags = [:]
+        saveUserCategorySchemeMutation(
+            store,
+            previous: previousStore,
+            reason: "reset-uncategorized"
+        )
+        return loadWithEnsuredCategoryScheme()
+    }
+
     private static func applyImportedCategorySchemeMetadata(to store: inout Store, importedFileURL url: URL) {
         let importedCreatedAt = importedSchemeCreatedAt(from: url)
         store.categoryScheme.currentCreatedAt = importedCreatedAt

--
Gitblit v1.9.3