Ariver
2026-05-11 ce804d58ae308981453c3ecdc9c3c2f14687d598
Prepare App Store sandbox build
3 files modified
1 files added
110 ■■■■ changed files
Apptag/ApptagApp.swift 13 ●●●●● patch | view | raw | blame | history
Apptag/ContentView.swift 85 ●●●●● patch | view | raw | blame | history
Apptag/TagLauncher.entitlements 8 ●●●●● patch | view | raw | blame | history
build.sh 4 ●●●● patch | view | raw | blame | history
Apptag/ApptagApp.swift
@@ -94,6 +94,9 @@
    // MARK: - Launch at Login (LaunchAgent, zero permissions)
    private static let launchAgentLabel = "com.apptag.launcher"
    static var supportsLaunchAtLogin: Bool {
        ProcessInfo.processInfo.environment["APP_SANDBOX_CONTAINER_ID"] == nil
    }
    private static var launchAgentURL: URL {
        FileManager.default.homeDirectoryForCurrentUser
@@ -101,6 +104,7 @@
    }
    static func enableLaunchAtLogin() {
        guard supportsLaunchAtLogin else { return }
        let plist: [String: Any] = [
            "Label": Self.launchAgentLabel,
            "ProgramArguments": ["open", Bundle.main.bundlePath, "--hide"],
@@ -118,6 +122,7 @@
    }
    static func disableLaunchAtLogin() {
        guard supportsLaunchAtLogin else { return }
        let uid = getuid()
        let task = Process()
        task.launchPath = "/bin/launchctl"
@@ -129,6 +134,12 @@
    /// On first launch, enable login item by default via LaunchAgent.
    /// Does NOT require App Management permission.
    private func setupLaunchAtLogin() {
        guard Self.supportsLaunchAtLogin else {
            if UserDefaults.standard.object(forKey: "launchAtLogin") == nil {
                UserDefaults.standard.set(false, forKey: "launchAtLogin")
            }
            return
        }
        let key = "launchAtLogin"
        if UserDefaults.standard.object(forKey: key) == nil {
            UserDefaults.standard.set(true, forKey: key)
@@ -635,6 +646,7 @@
            VStack(alignment: .leading, spacing: 0) {
                // Toggle row — centered as a rectangular block
                HStack(spacing: 20) {
                    if AppDelegate.supportsLaunchAtLogin {
                    Toggle(tr("settings.launchAtLogin"), isOn: $launchAtLogin)
                        .onChange(of: launchAtLogin) { _, enabled in
                            if enabled {
@@ -643,6 +655,7 @@
                                AppDelegate.disableLaunchAtLogin()
                            }
                        }
                    }
                    Toggle(tr("settings.showInDock"), isOn: $showDockIcon)
                    Toggle(tr("settings.hideAppNames"), isOn: $hideAppNames)
                }
Apptag/ContentView.swift
@@ -204,6 +204,7 @@
    @State private var successToast: String? = nil
    @State private var draggedTagNames: [String] = []  // live drag order
    @State private var dragItem: String? = nil          // currently dragged tag
    @State private var tagReorderFrames: [String: CGRect] = [:]
    @State private var hoveredContainer: String? = nil  // colored container lift
    // Fixed interaction for "Colorless Container": hover fills persistently; click clears.
    @State private var filledColorlessContainer: String? = nil
@@ -931,27 +932,27 @@
                        VStack(spacing: 4) {
                            ForEach(sortedTagNames, id: \.self) { tagName in
                                selectableTagItem(tagName)
                                    .onDrag {
                                        dragItem = tagName
                                        return NSItemProvider(object: tagName as NSString)
                                    .opacity(dragItem == tagName ? 0.72 : 1.0)
                                    .scaleEffect(dragItem == tagName ? 1.03 : 1.0)
                                    .background(
                                        GeometryReader { proxy in
                                            Color.clear.preference(
                                                key: TagReorderFramePreferenceKey.self,
                                                value: [tagName: proxy.frame(in: .named("tagReorderList"))]
                                            )
                                    }
                                    .onDrop(of: [.text], isTargeted: nil) { providers, _ in
                                        guard let fromName = dragItem,
                                              var names = draggedTagNames as [String]?,
                                              let fromIdx = names.firstIndex(of: fromName),
                                              let toIdx = names.firstIndex(of: tagName),
                                              fromIdx != toIdx
                                        else { return false }
                                        let toOffset = toIdx > fromIdx ? toIdx + 1 : toIdx
                                        names.move(fromOffsets: [fromIdx], toOffset: toOffset)
                                        draggedTagNames = names
                                        TagEditor.reorderTags(names)
                                        dragItem = nil
                                        return true
                                    )
                                    .zIndex(dragItem == tagName ? 1 : 0)
                                    .highPriorityGesture(tagReorderGesture(for: tagName))
                                    }
                            }
                        }.padding(12)
                    }.frame(width: 155)
                        .padding(12)
                        .coordinateSpace(name: "tagReorderList")
                        .onPreferenceChange(TagReorderFramePreferenceKey.self) { frames in
                            tagReorderFrames = frames
                        }
                    }
                    .frame(width: 155)
                }
                Rectangle().fill(.secondary.opacity(0.12)).frame(width: 1)
@@ -1033,6 +1034,36 @@
        .onTapGesture {
            if isSelected { selectedTagNames.remove(tagName) } else { selectedTagNames.insert(tagName) }
        }
    }
    private func tagReorderGesture(for tagName: String) -> some Gesture {
        DragGesture(minimumDistance: 4, coordinateSpace: .named("tagReorderList"))
            .onChanged { value in
                if dragItem == nil {
                    dragItem = tagName
                }
                guard dragItem == tagName else { return }
                reorderDraggedTag(at: value.location)
            }
            .onEnded { _ in
                dragItem = nil
                TagEditor.reorderTags(draggedTagNames)
            }
    }
    private func reorderDraggedTag(at location: CGPoint) {
        guard let fromName = dragItem,
              let targetName = tagReorderFrames.first(where: { $0.value.contains(location) })?.key,
              fromName != targetName,
              let fromIndex = draggedTagNames.firstIndex(of: fromName),
              let toIndex = draggedTagNames.firstIndex(of: targetName)
        else { return }
        withAnimation(.easeInOut(duration: 0.14)) {
            let destination = toIndex > fromIndex ? toIndex + 1 : toIndex
            draggedTagNames.move(fromOffsets: IndexSet(integer: fromIndex), toOffset: destination)
        }
        TagEditor.reorderTags(draggedTagNames)
    }
    private func confirmAssign() {
@@ -1269,16 +1300,6 @@
    func openApp(_ app: AppInfo) {
        appDragModeActive = false
        hideOverlay()
        if let bundleIdentifier = app.bundleIdentifier {
            NSWorkspace.shared.launchApplication(
                withBundleIdentifier: bundleIdentifier,
                options: [],
                additionalEventParamDescriptor: nil,
                launchIdentifier: nil
            )
            return
        }
        let configuration = NSWorkspace.OpenConfiguration()
        NSWorkspace.shared.openApplication(at: app.path, configuration: configuration)
    }
@@ -1286,6 +1307,14 @@
// MARK: - Tag Label
private struct TagReorderFramePreferenceKey: PreferenceKey {
    static var defaultValue: [String: CGRect] = [:]
    static func reduce(value: inout [String: CGRect], nextValue: () -> [String: CGRect]) {
        value.merge(nextValue(), uniquingKeysWith: { _, new in new })
    }
}
private struct TagLabel: Identifiable {
    var id: String { name }
    let name: String
Apptag/TagLauncher.entitlements
New file
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>com.apple.security.app-sandbox</key>
    <true/>
</dict>
</plist>
build.sh
@@ -24,6 +24,10 @@
    if [ -z "$CODESIGN_IDENTITY" ]; then
        echo "⚠️  APP_STORE=1 but CODESIGN_IDENTITY not set. Will ad-hoc sign with entitlements."
    fi
    if [ ! -f "$ENTITLEMENTS" ]; then
        echo "❌ APP_STORE=1 requires entitlements at: $ENTITLEMENTS"
        exit 1
    fi
fi
echo "==> Cleaning..."