From 7e7e7339b903faf43019413e84839d1aba0a2115 Mon Sep 17 00:00:00 2001 From: Axel Courty Date: Tue, 26 May 2026 01:47:44 +0200 Subject: [PATCH 1/3] Lock down Pro features for free users (no UI/data leaks) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Audit utilisateur : les features Pro fuitaient pour les users free : - Onglets Shelf / IA Code / Clipboard visibles dans la barre (paywall au clic mais existence évidente) - La live-activity "AI session active" s'affichait dans l'encoche fermée (compact view code assistant), trahissant la feature - Managers scannaient en arrière-plan : ClaudeCode JSONL, Codex SQLite, Copilot logs, clipboard pasteboard — collecte de données sans bénéfice - DragDetector installé même pour les users sans Shelf (drag réagit) Fix 5 couches : 1. **NotchViews.requiresPro** + gate dans `isVisibleInExpandedNotch` → onglets Pro disparaissent de la barre pour les users free. NotchIAHeader + TabSelectionView observent LicenseManager pour re-render à l'activation/révocation Pro à la volée. 2. **shouldShowCodeAssistantCompactActivity** (ContentView) → guard isPro → pas de compact view "AI active" pour les users free. 3. **ClipboardManager.checkPasteboard** : guard isPro → aucun item enregistré, l'historique reste vide. 4. **ClaudeCodeManager.scanForSessions / CodexManager.scanForSessions / CopilotManager.checkCopilotStatus** : guard isPro → pas de scan de fichiers, pas de détection de session. 5. **AppDelegate.setupDragDetectorForScreen** : guard isPro → pas de région de drag installée si Shelf inaccessible. ProGate sur les onglets reste en place comme défense en profondeur. La clé admin (`nia_admin_…`) débloque tout comme prévu. Co-Authored-By: Claude Opus 4.7 --- NotchIA/ContentView.swift | 4 ++++ NotchIA/NotchIAApp.swift | 7 ++++++- NotchIA/components/Notch/NotchIAHeader.swift | 3 +++ NotchIA/components/Tabs/TabSelectionView.swift | 4 ++++ NotchIA/enums/generic.swift | 18 ++++++++++++++++++ NotchIA/managers/ClaudeCodeManager.swift | 8 ++++++++ NotchIA/managers/ClipboardManager.swift | 6 +++++- NotchIA/managers/CodexManager.swift | 4 ++++ NotchIA/managers/CopilotManager.swift | 6 ++++++ 9 files changed, 58 insertions(+), 2 deletions(-) diff --git a/NotchIA/ContentView.swift b/NotchIA/ContentView.swift index c88eac5..0ff96ba 100644 --- a/NotchIA/ContentView.swift +++ b/NotchIA/ContentView.swift @@ -172,6 +172,10 @@ struct ContentView: View { } private var shouldShowCodeAssistantCompactActivity: Bool { + // Pro lockdown : la live-activity "AI session active" est une feature + // Pro (onglet `.claudeCode` est requiresPro). Free users ne la voient + // jamais, peu importe leurs Defaults — pas de fuite d'existence. + guard LicenseManager.shared.state.isPro else { return false } guard !coordinator.expandingView.show, !vm.hideOnClosed, Defaults[.enableClaudeCode], diff --git a/NotchIA/NotchIAApp.swift b/NotchIA/NotchIAApp.swift index 0154ae6..48f5244 100644 --- a/NotchIA/NotchIAApp.swift +++ b/NotchIA/NotchIAApp.swift @@ -280,8 +280,13 @@ class AppDelegate: NSObject, NSApplicationDelegate { } private func setupDragDetectorForScreen(_ screen: NSScreen) { + // Pro lockdown : le drag-detector existe pour ouvrir l'onglet Shelf + // au drag-and-drop d'un fichier sur l'encoche. Shelf est requiresPro, + // donc on n'installe pas le détecteur pour les users free — pas de + // fuite d'existence de la feature. + guard LicenseManager.shared.state.isPro else { return } guard let uuid = screen.displayUUID else { return } - + let screenFrame = screen.frame let notchHeight = openNotchSize.height let notchWidth = openNotchSize.width diff --git a/NotchIA/components/Notch/NotchIAHeader.swift b/NotchIA/components/Notch/NotchIAHeader.swift index 7efc834..6ba9c99 100644 --- a/NotchIA/components/Notch/NotchIAHeader.swift +++ b/NotchIA/components/Notch/NotchIAHeader.swift @@ -12,6 +12,9 @@ struct NotchIAHeader: View { @EnvironmentObject var vm: NotchIAViewModel @ObservedObject var coordinator = NotchIAViewCoordinator.shared @ObservedObject var screenLockManager = ScreenLockManager.shared + // Observé pour que le filtre `.requiresPro` de la barre d'onglets + // re-rende quand la licence change (activation/révocation à la volée). + @ObservedObject private var license = LicenseManager.shared @Default(.appGradientTheme) private var gradientTheme @Namespace var animation diff --git a/NotchIA/components/Tabs/TabSelectionView.swift b/NotchIA/components/Tabs/TabSelectionView.swift index 8f5c89f..65835ae 100644 --- a/NotchIA/components/Tabs/TabSelectionView.swift +++ b/NotchIA/components/Tabs/TabSelectionView.swift @@ -10,6 +10,10 @@ import SwiftUI struct TabSelectionView: View { @ObservedObject var coordinator = NotchIAViewCoordinator.shared + // Observé pour que le filtre `.requiresPro` re-rende quand la licence + // change (activation/révocation à la volée → onglets Pro apparaissent + // ou disparaissent immédiatement). + @ObservedObject private var license = LicenseManager.shared @Default(.appGradientTheme) private var theme @Namespace var animation diff --git a/NotchIA/enums/generic.swift b/NotchIA/enums/generic.swift index 028fc67..fbb6d6f 100644 --- a/NotchIA/enums/generic.swift +++ b/NotchIA/enums/generic.swift @@ -73,7 +73,25 @@ extension NotchViews { } } + /// Onglets gated derrière la licence Pro. Cachés ENTIÈREMENT de la barre + /// d'onglets pour les users free — pas seulement de paywall au clic, mais + /// pas de fuite d'existence non plus. La détection associée en background + /// (ClaudeCode JSONL, Clipboard pasteboard, drag-and-drop Shelf) est aussi + /// désactivée pour les users free dans leurs managers respectifs. + var requiresPro: Bool { + switch self { + case .shelf, .claudeCode, .clipboard: return true + case .media, .calendar, .digest, .pomodoro: return false + } + } + + @MainActor var isVisibleInExpandedNotch: Bool { + // Pro lockdown : si l'onglet est Pro et l'utilisateur n'a pas de + // licence active, on le masque entièrement de la barre. + if requiresPro && !LicenseManager.shared.state.isPro { + return false + } switch self { case .media: return Defaults[.showMediaTab] diff --git a/NotchIA/managers/ClaudeCodeManager.swift b/NotchIA/managers/ClaudeCodeManager.swift index 8c287d4..53e34ad 100644 --- a/NotchIA/managers/ClaudeCodeManager.swift +++ b/NotchIA/managers/ClaudeCodeManager.swift @@ -202,6 +202,14 @@ final class ClaudeCodeManager: ObservableObject { /// scan timer doesn't stall the main actor for 5-50ms (or much more /// when the system is under load). func scanForSessions() async { + // Pro lockdown : pas de scan des sessions Claude Code pour les users + // sans licence Pro. L'onglet IA Code (`.claudeCode`) est requiresPro + // et la live-activity compact dans l'encoche aussi — pas de scan, + // pas de session trackée, pas de fuite d'existence de la feature. + guard LicenseManager.shared.state.isPro else { + await MainActor.run { self.availableSessions = [] } + return + } let fm = FileManager.default var sessions: [ClaudeSession] = [] diff --git a/NotchIA/managers/ClipboardManager.swift b/NotchIA/managers/ClipboardManager.swift index 5646925..b3c4892 100644 --- a/NotchIA/managers/ClipboardManager.swift +++ b/NotchIA/managers/ClipboardManager.swift @@ -130,7 +130,11 @@ final class ClipboardManager: ObservableObject { } private func checkPasteboard() { - guard Defaults[.enableClipboardHistory] else { return } + // Pro lockdown : aucune entrée du presse-papiers n'est enregistrée + // pour les users sans licence Pro. L'historique est une feature Pro + // (l'onglet `.clipboard` est requiresPro) — `items` reste vide tant + // que l'utilisateur n'a pas activé sa licence. + guard Defaults[.enableClipboardHistory], LicenseManager.shared.state.isPro else { return } let pb = NSPasteboard.general guard pb.changeCount != lastChangeCount else { return } lastChangeCount = pb.changeCount diff --git a/NotchIA/managers/CodexManager.swift b/NotchIA/managers/CodexManager.swift index a8390d4..7b16c26 100644 --- a/NotchIA/managers/CodexManager.swift +++ b/NotchIA/managers/CodexManager.swift @@ -102,6 +102,10 @@ final class CodexManager: ObservableObject { } func scanForSessions() { + // Pro lockdown : pas de scan des sessions Codex pour les users free. + // L'onglet IA Code (`.claudeCode`) est requiresPro et son provider + // Codex aussi. Pas de fuite d'existence. + guard LicenseManager.shared.state.isPro else { return } // Snapshot caches (value types) so the background task has its own copy. let scanner = CodexSessionScanner( indexFile: indexFile, diff --git a/NotchIA/managers/CopilotManager.swift b/NotchIA/managers/CopilotManager.swift index fb6eb75..2c73662 100644 --- a/NotchIA/managers/CopilotManager.swift +++ b/NotchIA/managers/CopilotManager.swift @@ -130,6 +130,12 @@ final class CopilotManager: ObservableObject { } private func checkCopilotStatus() { + // Pro lockdown : pas de scan des logs Copilot pour les users free. + // Provider Copilot affilié à l'onglet IA Code requiresPro. + guard LicenseManager.shared.state.isPro else { + if isActive { isActive = false } + return + } statusTicket &+= 1 let ticket = statusTicket let reader = logReader From 4058c56fee9d4d7c99cad7b54a27736bc5c40755 Mon Sep 17 00:00:00 2001 From: Anka Date: Mon, 25 May 2026 23:48:55 +0000 Subject: [PATCH 2/3] Set version to v2.8.7 (build 20807) --- NotchIA.xcodeproj/project.pbxproj | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/NotchIA.xcodeproj/project.pbxproj b/NotchIA.xcodeproj/project.pbxproj index d980862..a7e4d54 100644 --- a/NotchIA.xcodeproj/project.pbxproj +++ b/NotchIA.xcodeproj/project.pbxproj @@ -1277,7 +1277,7 @@ "CODE_SIGN_IDENTITY[sdk=macosx*]" = "-"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 20806; + CURRENT_PROJECT_VERSION = 20807; DEVELOPMENT_TEAM = ""; ENABLE_HARDENED_RUNTIME = NO; GENERATE_INFOPLIST_FILE = YES; @@ -1285,7 +1285,7 @@ INFOPLIST_KEY_CFBundleDisplayName = NotchIAXPCHelper; INFOPLIST_KEY_NSHumanReadableCopyright = ""; MACOSX_DEPLOYMENT_TARGET = 15.0; - MARKETING_VERSION = 2.8.6; + MARKETING_VERSION = 2.8.7; PRODUCT_BUNDLE_IDENTIFIER = com.coaxel2.notchia.NotchIAXPCHelper; PRODUCT_NAME = "$(TARGET_NAME)"; REGISTER_APP_GROUPS = YES; @@ -1303,7 +1303,7 @@ "CODE_SIGN_IDENTITY[sdk=macosx*]" = "-"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 20806; + CURRENT_PROJECT_VERSION = 20807; DEVELOPMENT_TEAM = ""; ENABLE_HARDENED_RUNTIME = NO; GENERATE_INFOPLIST_FILE = YES; @@ -1311,7 +1311,7 @@ INFOPLIST_KEY_CFBundleDisplayName = NotchIAXPCHelper; INFOPLIST_KEY_NSHumanReadableCopyright = ""; MACOSX_DEPLOYMENT_TARGET = 15.0; - MARKETING_VERSION = 2.8.6; + MARKETING_VERSION = 2.8.7; PRODUCT_BUNDLE_IDENTIFIER = com.coaxel2.notchia.NotchIAXPCHelper; PRODUCT_NAME = "$(TARGET_NAME)"; REGISTER_APP_GROUPS = YES; @@ -1462,7 +1462,7 @@ "CODE_SIGN_IDENTITY[sdk=macosx*]" = "-"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 20806; + CURRENT_PROJECT_VERSION = 20807; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_ASSET_PATHS = "\"NotchIA/Preview Content\""; DEVELOPMENT_TEAM = ""; @@ -1490,7 +1490,7 @@ "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 15.0; - MARKETING_VERSION = 2.8.6; + MARKETING_VERSION = 2.8.7; PRODUCT_BUNDLE_IDENTIFIER = com.coaxel2.notchia; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -1518,7 +1518,7 @@ CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; COPY_PHASE_STRIP = YES; - CURRENT_PROJECT_VERSION = 20806; + CURRENT_PROJECT_VERSION = 20807; DEAD_CODE_STRIPPING = YES; DEPLOYMENT_POSTPROCESSING = YES; DEVELOPMENT_ASSET_PATHS = "\"NotchIA/Preview Content\""; @@ -1546,7 +1546,7 @@ "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 15.0; - MARKETING_VERSION = 2.8.6; + MARKETING_VERSION = 2.8.7; PRODUCT_BUNDLE_IDENTIFIER = com.coaxel2.notchia; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; From 1a689d35481c88afe1b13bab909a4b08e347c6cd Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 25 May 2026 23:52:29 +0000 Subject: [PATCH 3/3] Update version to v2.8.7 and appcast --- updater/appcast.xml | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/updater/appcast.xml b/updater/appcast.xml index dfb259b..06fcb33 100644 --- a/updater/appcast.xml +++ b/updater/appcast.xml @@ -32,6 +32,16 @@ --> + + 2.8.7 + Mon, 25 May 2026 23:52:28 +0000 + https://github.com/coaxel2/NotchIA/releases + 20807 + 2.8.7 + 15.0 + + + 2.8.6 Mon, 25 May 2026 23:37:23 +0000 @@ -52,16 +62,6 @@ - - 2.8.4 - Mon, 25 May 2026 23:13:24 +0000 - https://github.com/coaxel2/NotchIA/releases - 20804 - 2.8.4 - 15.0 - - - 2.7.3 Mon, 24 Nov 2025 08:07:37 +0000