Compose Desktop, prêt pour la prod
Compose Multiplatform dessine l'UI. Nucleus en fait une vraie app desktop — chrome natif, APIs OS, packaging et stores. La même base Kotlin que tes apps Android, iOS et web. Pas de seconde stack. Pas de JNI circus.
Sur Android, Kotlin et Compose sont citoyens de première classe. Sur iOS et le web, même histoire. Le desktop avait Compose pour l'UI — et te laissait inventer le reste : chrome natif, APIs OS, signature, auto-update, stores. Compose Desktop vanilla s'arrête à la fenêtre. Nucleus est la couche de production qui manquait à ton équipe Kotlin.
Il livre aussi des composants — une seule API Compose, chaque plateforme.
Chacun remplace quatre moteurs natifs qu'il faudrait sinon écrire, tester et garder synchronisés — derrière un seul composable identique sur Android, iOS, web et desktop.
PdfReader(state)dev.nucleusframework:pdfiumWebView(state)dev.nucleusframework:composewebviewTextureView et NativeView placent textures GPU et widgets natifs dans Compose. L'auto-update différentiel ne télécharge que les blocs modifiés. Le Task Scheduler lance du travail en arrière-plan comme WorkManager, même app fermée. Et toute la surface OS — notifications, tray, raccourcis globaux, mode sombre — une API Kotlin par capacité.
Tao est la crate de fenêtrage Rust derrière Tauri 2. Nucleus en fait le backend de fenêtre par défaut — sans AWT — : TextureView pour les textures GPU externes, NativeView pour les widgets natifs, Wayland natif, multi-touch, stylet et chrome via window scaffold, dans le même arbre Compose.
Le backend Tao projette ta sémantique Compose sur NSAccessibility, UI Automation et AT-SPI. VoiceOver, Narrator et Orca fonctionnent directement — pas de câblage supplémentaire, pas de code spécifique par plateforme. Et l'accessibilité compte plus que jamais : alors que les agents IA interagissent de plus en plus avec les applications, ils s'appuient sur les mêmes arbres d'accessibilité structurés pour lire et contrôler les programmes.
Une UI écrite une fois en Compose, rendue dans le style desktop voulu — macOS 26 Liquid Glass, Windows 11 Fluent, Ubuntu Yaru, IntelliJ Jewel ou Material 3 multiplateforme. macOS, Fluent et Yaru sont des design systems open-source sur Maven Central (avec fenêtres décorées basées sur Nucleus) ; Jewel est le toolkit JetBrains ; Material 3 est le repli Nucleus.
Choisissez un toolkit par OS, ou mélangez librement — chaque design system tourne sur chaque plateforme. Vous pouvez livrer un build Fluent sur macOS le temps de prototyper le look natif. Dans tous les cas, le DecoratedWindow reste le même Composable.
Le même code Kotlin, deux runtimes : une image native GraalVM pour un démarrage à froid instantané et une empreinte minuscule, ou un JDK moderne avec cache AOT où le JIT de HotSpot s'approche du C++ et de Rust sur les chemins chauds. Même source. Même build.
Démarrage à froid instantané. Empreinte minuscule.
L'app entière est compilée AOT en binaire autonome. Pas de démarrage JVM, pas de chargement de classes — le process est vivant en une demi-seconde. La plus petite empreinte mémoire du marché.
nucleus.graalvm-runtimeDébit JIT au sommet. Plugins, agents, réflexion totale.
Le JIT C2 de HotSpot est le compilateur le plus mûr jamais construit. Le cache AOT de JDK 25 amorce les métadonnées de classes : le warm-up disparaît — et tout ce que le closed-world abandonne reste accessible : chargement dynamique, réflexion totale, agents JVM, moteurs de scripting, plugins et extensions à chaud.
nucleus.aot-runtime* Démarrage à froid et RAM mesurés sur Windows 11 avec un build Hello World. La taille du binaire correspond à l’installateur NSIS avec compression maximale.
Win32 ITaskbarList3. NSUserNotifications. freedesktop D-Bus. IOKit. ScreenCaptureKit. Chaque API desktop est son propre petit calvaire — langage différent, modèle de threading différent, conventions différentes. Nucleus enveloppe chacune dans une fonction Kotlin qui paraît évidente. Résultat : une abstraction multiplateforme plus simple que l'originale, sur chaque plateforme.
1// SwiftUI app (2025) — still goes through UserNotifications + UNNotificationCenter2import SwiftUI3import UserNotifications45@main6struct MyApp: App {7 @UIApplicationDelegateAdaptor(NotifDelegate.self) var delegate8 var body: some Scene { WindowGroup { ContentView() } }9}1011func postBuildNotif() async {12 let center = UNUserNotificationCenter.current()1314 // 1. Authorization15 let ok = try? await center.requestAuthorization(options: [.alert, .sound])16 guard ok == true else { return }1718 // 2. Declare the actions and register the category up-front19 let reveal = UNNotificationAction(identifier: "REVEAL",20 title: "Reveal in Finder", options: [.foreground])21 let copy = UNNotificationAction(identifier: "COPY",22 title: "Copy path", options: [])23 let share = UNNotificationAction(identifier: "SHARE",24 title: "Share…", options: [.foreground])2526 let cat = UNNotificationCategory(identifier: "BUILD_DONE",27 actions: [reveal, copy, share], intentIdentifiers: [],28 options: .customDismissAction)29 center.setNotificationCategories([cat])3031 // 3. Build content32 let content = UNMutableNotificationContent()33 content.title = "Build complete"34 content.body = "Installer ready. What now?"35 content.categoryIdentifier = "BUILD_DONE"36 content.sound = .default3738 let req = UNNotificationRequest(identifier: UUID().uuidString,39 content: content, trigger: nil)40 try? await center.add(req)41}4243// 4. Delegate to receive callbacks (must be retained, AppDelegate is easiest)44class NotifDelegate: NSObject, UIApplicationDelegate,45 UNUserNotificationCenterDelegate {46 func application(_ a: UIApplication, didFinishLaunchingWithOptions o:47 [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {48 UNUserNotificationCenter.current().delegate = self49 return true50 }5152 func userNotificationCenter(_ c: UNUserNotificationCenter,53 didReceive r: UNNotificationResponse,54 withCompletionHandler done: @escaping () -> Void) {55 switch r.actionIdentifier {56 case "REVEAL": revealInFinder(file)57 case "COPY": copyPath(file)58 case "SHARE": shareSheet(file)59 case UNNotificationDismissActionIdentifier:60 telemetry.log("notif_dismissed")61 default: break62 }63 done()64 }65}66// + Info.plist NSUserNotificationsUsageDescription67// + handle authorization revoked mid-app-lifecycle1import dev.nucleusframework.notification.notify2import dev.nucleusframework.notification.NotificationAction34notify(5 title = "Build complete",6 body = "Installer ready. What now?",7 icon = Icon.AppIcon,8 actions = listOf(9 NotificationAction("reveal", "Reveal in Finder") {10 Desktop.open(file.parent)11 },12 NotificationAction("copy", "Copy path") {13 Clipboard.put(file.absolutePath)14 },15 NotificationAction("share", "Share…") { shareSheet(file) },16 ),17 onDismiss = { telemetry.log("notif_dismissed") },18)Nucleus livre des GitHub Actions réutilisables qui compilent, signent, notarisent, empaquettent et publient pour chaque desktop — sans YAML copié-collé. Six actions composites, un workflow de référence, un seul push de tag.
git push tag v1.0.0setup-nucleusJBR 25 ou Liberica NIK, cache Gradle, Node, outils de packaging Linux — une étape, tout runner.
setup-macos-signingTrousseau temporaire, .p12 importé depuis les secrets, identités exposées aux étapes suivantes.
build-macos-universalFusion lipo arm64 + x64, re-sign inside-out, notarisation via notarytool, staple — un DMG en sortie.
build-windows-appxbundleFusion amd64 + arm64 .appx en .msixbundle, signature SignTool — prêt pour le Microsoft Store.
generate-update-ymlSHA-512 de chaque installeur, génère latest-*.yml et les block maps electron-builder pour ne télécharger que les blocs modifiés.
publish-releasegh release create avec les bons assets, marque -alpha / -beta / -rc en pre-release automatiquement.
Une question, une migration coincée, ou juste pour lire. #nucleus est sur le Slack officiel Kotlin.