diff --git a/build.gradle.kts b/build.gradle.kts index f3b70d15..3ae07410 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -90,7 +90,7 @@ subprojects { jvmArgs("--add-opens=java.base/java.nio=ALL-UNNAMED") // Suppress ByteBuddy/Mockito dynamic agent loading warnings (Java 21+) jvmArgs("-XX:+EnableDynamicAgentLoading") - // Increase test JVM memory with a stable configuration + jvmArgs("--enable-native-access=ALL-UNNAMED") minHeapSize = "512m" maxHeapSize = "2g" // Parallel test execution for better performance @@ -166,6 +166,7 @@ subprojects { jvmArgs("-Xshare:auto", "-Djdk.instrument.traceUsage=false") jvmArgs("--add-opens=java.base/java.nio=ALL-UNNAMED") jvmArgs("-XX:+EnableDynamicAgentLoading") + jvmArgs("--enable-native-access=ALL-UNNAMED") maxHeapSize = "2g" dependsOn("testClasses") } diff --git a/frontend/core/local-db/src/commonMain/kotlin/at/mocode/frontend/core/localdb/LocalDbModule.kt b/frontend/core/local-db/src/commonMain/kotlin/at/mocode/frontend/core/localdb/LocalDbModule.kt index 6dd1c31f..43fcc67f 100644 --- a/frontend/core/local-db/src/commonMain/kotlin/at/mocode/frontend/core/localdb/LocalDbModule.kt +++ b/frontend/core/local-db/src/commonMain/kotlin/at/mocode/frontend/core/localdb/LocalDbModule.kt @@ -2,12 +2,6 @@ package at.mocode.frontend.core.localdb import org.koin.dsl.module -/** - * Thin wrapper around SQLDelight `AppDatabase` creation. - * - * The platform-specific part is the `DatabaseDriverFactory` (expect/actual), - * which provides the appropriate SQLDelight driver (JVM sqlite driver, JS WebWorkerDriver, ...). - */ class DatabaseProvider( private val driverFactory: DatabaseDriverFactory ) { @@ -17,9 +11,6 @@ class DatabaseProvider( } } -/** - * Koin module to provide the SQLDelight database for all frontend targets. - */ val localDbModule = module { single { DatabaseDriverFactory() } single { DatabaseProvider(get()) } diff --git a/frontend/core/network/src/jvmMain/kotlin/at/mocode/frontend/core/network/sync/JvmP2pSyncService.kt b/frontend/core/network/src/jvmMain/kotlin/at/mocode/frontend/core/network/sync/JvmP2pSyncService.kt index 101d3b57..ff939447 100644 --- a/frontend/core/network/src/jvmMain/kotlin/at/mocode/frontend/core/network/sync/JvmP2pSyncService.kt +++ b/frontend/core/network/src/jvmMain/kotlin/at/mocode/frontend/core/network/sync/JvmP2pSyncService.kt @@ -45,6 +45,7 @@ class JvmP2pSyncService : P2pSyncService { } // Prozessweiter, portbasierter Guard + println("[P2P Server] Versuche Port $port zu reservieren...") if (!startedPorts.add(port)) { println("[P2P Server] Bereits gestartet (Prozess) auf Port $port – idempotent, kein neuer Bind") return @@ -79,7 +80,6 @@ class JvmP2pSyncService : P2pSyncService { } }.start(wait = false) currentPort = port - println("[P2P Server] Gestartet auf Port $port") } catch (e: Exception) { // Start fehlgeschlagen -> Port-Lock wieder freigeben startedPorts.remove(port) diff --git a/frontend/features/device-initialization/src/commonMain/kotlin/at/mocode/frontend/features/device/initialization/presentation/DeviceInitializationScreen.kt b/frontend/features/device-initialization/src/commonMain/kotlin/at/mocode/frontend/features/device/initialization/presentation/DeviceInitializationScreen.kt index 022d0fc3..130474ff 100644 --- a/frontend/features/device-initialization/src/commonMain/kotlin/at/mocode/frontend/features/device/initialization/presentation/DeviceInitializationScreen.kt +++ b/frontend/features/device-initialization/src/commonMain/kotlin/at/mocode/frontend/features/device/initialization/presentation/DeviceInitializationScreen.kt @@ -33,6 +33,8 @@ import androidx.compose.ui.unit.sp import at.mocode.frontend.features.device.initialization.domain.DeviceInitializationValidator import at.mocode.frontend.features.device.initialization.domain.model.AppThemeSetting import at.mocode.frontend.features.device.initialization.domain.model.NetworkRole +import kotlinx.coroutines.delay +import kotlin.time.Duration.Companion.milliseconds @Composable private fun DiscoveryRadar( @@ -94,7 +96,7 @@ fun DeviceInitializationScreen( // Automatische Discovery starten LaunchedEffect(Unit) { viewModel.startDiscovery() - roleSelectorFocus.requestFocus() + delay(100.milliseconds); withFrameMillis { roleSelectorFocus.requestFocus() } } Surface( diff --git a/frontend/features/device-initialization/src/jvmMain/kotlin/at/mocode/frontend/features/device/initialization/presentation/DeviceInitializationConfig.jvm.kt b/frontend/features/device-initialization/src/jvmMain/kotlin/at/mocode/frontend/features/device/initialization/presentation/DeviceInitializationConfig.jvm.kt index e48cfd1a..a7db9ab8 100644 --- a/frontend/features/device-initialization/src/jvmMain/kotlin/at/mocode/frontend/features/device/initialization/presentation/DeviceInitializationConfig.jvm.kt +++ b/frontend/features/device-initialization/src/jvmMain/kotlin/at/mocode/frontend/features/device/initialization/presentation/DeviceInitializationConfig.jvm.kt @@ -48,12 +48,6 @@ actual fun DeviceInitializationConfig( val focusManager = LocalFocusManager.current val (_, sharedKeyFocus, backupPathFocus, clientNameFocus, clientRoleFocus) = remember { FocusRequester.createRefs() } - LaunchedEffect(Unit) { - if (settings.deviceName.isEmpty()) { - deviceNameFocus.requestFocus() - } - } - Card( modifier = Modifier.fillMaxWidth(), shape = MaterialTheme.shapes.large, @@ -66,7 +60,7 @@ actual fun DeviceInitializationConfig( value = settings.deviceName, onValueChange = { viewModel.updateSettings { s -> s.copy(deviceName = it) } }, label = "Gerätename", - helpDescription = "Ein eindeutiger Name für diesen PC (z.B. 'Richter-Springplatz').", + helpDescription = "Ein eindeutiger Name für diesen PC (z.B. Richter-Springplatz).", placeholder = "z.B. Meldestelle-PC-1", isError = settings.deviceName.isNotEmpty() && !DeviceInitializationValidator.isNameValid(settings.deviceName), errorMessage = "Mindestens ${DeviceInitializationValidator.MIN_NAME_LENGTH} Zeichen erforderlich.", @@ -77,24 +71,43 @@ actual fun DeviceInitializationConfig( compact = true ) - // NETZWERK-INTERFACES (EXPERTEN-MODUS) val interfaces = remember { NetworkInterface.getNetworkInterfaces().toList() - .filter { it.isUp && !it.isLoopback && it.inetAddresses.hasMoreElements() } + .filter { it.isUp && !it.isLoopback && it.inetAddresses.hasMoreElements() && !it.name.startsWith("br-") && !it.name.startsWith("docker") && !it.name.startsWith("veth") } .map { ni -> val friendlyName = when { - ni.displayName.contains("wlan", ignoreCase = true) || ni.displayName.contains("wi-fi", ignoreCase = true) || ni.name.contains("wlan", ignoreCase = true) -> "🌐 WLAN" - ni.displayName.contains("eth", ignoreCase = true) || ni.displayName.contains("ethernet", ignoreCase = true) || ni.name.contains("eth", ignoreCase = true) || ni.name.contains("en", ignoreCase = true) -> "🔌 Ethernet" + ni.displayName.contains("wlan", ignoreCase = true) || ni.displayName.contains( + "wi-fi", + ignoreCase = true + ) || ni.name.contains("wlan", ignoreCase = true) -> "🌐 WLAN" + + ni.displayName.contains("eth", ignoreCase = true) || ni.displayName.contains( + "ethernet", + ignoreCase = true + ) || ni.name.contains("eth", ignoreCase = true) || ni.name.contains( + "en", + ignoreCase = true + ) -> "🔌 Ethernet" + else -> "💻 " + ni.displayName } - val address = ni.inetAddresses.asSequence().firstOrNull { !it.isLinkLocalAddress && it.hostAddress.indexOf(':') == -1 }?.hostAddress - ?: ni.inetAddresses.nextElement().hostAddress + val address = ni.inetAddresses.asSequence() + .firstOrNull { !it.isLinkLocalAddress && it.hostAddress.indexOf(":") == -1 }?.hostAddress + ?: ni.inetAddresses.nextElement().hostAddress val isConnected = !ni.isLoopback && ni.isUp && ni.interfaceAddresses.any { - it.address.isSiteLocalAddress || it.address.hostAddress.startsWith("192.168") || it.address.hostAddress.startsWith("10.") + it.address.isSiteLocalAddress || it.address.hostAddress.startsWith("192.168") || it.address.hostAddress.startsWith( + "10." + ) } - InterfaceInfo(id = "$friendlyName ($address)", name = friendlyName, address = address, hardwareName = ni.name, isConnected = isConnected) + InterfaceInfo( + id = "$friendlyName ($address)", + name = friendlyName, + address = address, + hardwareName = ni.name, + isConnected = isConnected + ) } } @@ -120,12 +133,17 @@ actual fun DeviceInitializationConfig( Surface( onClick = { if (!uiState.isLocked) viewModel.updateSettings { s -> s.copy(networkInterface = info.id) } }, shape = MaterialTheme.shapes.medium, - color = if (isSelected) MaterialTheme.colorScheme.primaryContainer else MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.3f), + color = if (isSelected) MaterialTheme.colorScheme.primaryContainer else MaterialTheme.colorScheme.surfaceVariant.copy( + alpha = 0.3f + ), border = if (isSelected) BorderStroke(2.dp, MaterialTheme.colorScheme.primary) else null, modifier = Modifier.fillMaxWidth() ) { Row(Modifier.padding(12.dp), verticalAlignment = Alignment.CenterVertically) { - Box(Modifier.size(10.dp).background(if (info.isConnected) Color(0xFF4CAF50) else Color(0xFFF44336), CircleShape)) + Box( + Modifier.size(10.dp) + .background(if (info.isConnected) Color(0xFF4CAF50) else Color(0xFFF44336), CircleShape) + ) Spacer(Modifier.width(12.dp)) Column(Modifier.weight(1f)) { Text(info.name, style = MaterialTheme.typography.bodyMedium, fontWeight = FontWeight.Bold) @@ -137,13 +155,12 @@ actual fun DeviceInitializationConfig( } } - // SICHERHEITSSCHLÜSSEL var passwordVisible by remember { mutableStateOf(false) } MsTextField( value = settings.sharedKey, onValueChange = { viewModel.updateSettings { s -> s.copy(sharedKey = it) } }, label = "Sicherheitsschlüssel (Sync-Key)", - helpDescription = "Das 'Turnier-Passwort'. Muss auf allen Geräten gleich sein.", + helpDescription = "Das Turnier-Passwort. Muss auf allen Geräten gleich sein.", placeholder = "Mindestens 8 Zeichen", isError = settings.sharedKey.isNotEmpty() && !DeviceInitializationValidator.isKeyValid(settings.sharedKey), errorMessage = "Mindestens ${DeviceInitializationValidator.MIN_KEY_LENGTH} Zeichen erforderlich.", @@ -157,7 +174,6 @@ actual fun DeviceInitializationConfig( compact = true ) - // CLIENT-VERBINDUNG-FEEDBACK if (settings.networkRole == NetworkRole.CLIENT && !uiState.isLocked) { val masterSelected = uiState.selectedMaster != null val canConnect = masterSelected && settings.sharedKey.isNotBlank() @@ -170,13 +186,19 @@ actual fun DeviceInitializationConfig( else -> MaterialTheme.colorScheme.primaryContainer.copy(alpha = 0.1f) }, shape = MaterialTheme.shapes.medium, - border = BorderStroke(1.dp, when (uiState.connectionStatus) { - ConnectionStatus.CONNECTED -> Color(0xFF4CAF50) - ConnectionStatus.FAILED -> Color(0xFFF44336) - else -> MaterialTheme.colorScheme.outlineVariant - }) + border = BorderStroke( + 1.dp, when (uiState.connectionStatus) { + ConnectionStatus.CONNECTED -> Color(0xFF4CAF50) + ConnectionStatus.FAILED -> Color(0xFFF44336) + else -> MaterialTheme.colorScheme.outlineVariant + } + ) ) { - Column(Modifier.padding(16.dp), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.spacedBy(12.dp)) { + Column( + Modifier.padding(16.dp), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(12.dp) + ) { Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(12.dp)) { when (uiState.connectionStatus) { ConnectionStatus.CONNECTING -> CircularProgressIndicator(Modifier.size(20.dp), strokeWidth = 2.dp) @@ -210,7 +232,6 @@ actual fun DeviceInitializationConfig( } } - // BACKUP & DRUCKER MsFilePicker( label = "Backup-Verzeichnis (Plan-USB)", selectedPath = settings.backupPath, @@ -246,10 +267,13 @@ actual fun DeviceInitializationConfig( ) } - // MASTER: ERWARTETE CLIENTS if (settings.networkRole == NetworkRole.MASTER && !uiState.isLocked) { HorizontalDivider(Modifier.padding(vertical = 8.dp)) - Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically) { + Row( + Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { Text("👥 Erwartete Clients", style = MaterialTheme.typography.titleSmall) TextButton(onClick = { viewModel.addExpectedClient() }) { Icon(Icons.Default.Add, null, Modifier.size(18.dp)) @@ -295,7 +319,12 @@ actual fun DeviceInitializationConfig( }, trailingContent = { IconButton(onClick = { viewModel.removeExpectedClient(index) }) { - Icon(Icons.Default.Delete, null, tint = MaterialTheme.colorScheme.error, modifier = Modifier.size(20.dp)) + Icon( + Icons.Default.Delete, + null, + tint = MaterialTheme.colorScheme.error, + modifier = Modifier.size(20.dp) + ) } }, colors = ListItemDefaults.colors(containerColor = MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.5f)), @@ -307,4 +336,10 @@ actual fun DeviceInitializationConfig( } } -private data class InterfaceInfo(val id: String, val name: String, val address: String, val hardwareName: String, val isConnected: Boolean) +private data class InterfaceInfo( + val id: String, + val name: String, + val address: String, + val hardwareName: String, + val isConnected: Boolean +) diff --git a/frontend/features/ping-feature/build.gradle.kts b/frontend/features/ping-feature/build.gradle.kts index f9afe15d..9d2f150d 100644 --- a/frontend/features/ping-feature/build.gradle.kts +++ b/frontend/features/ping-feature/build.gradle.kts @@ -2,9 +2,6 @@ import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl -/** - * Dieses Modul kapselt die gesamte UI und Logik für das Ping-Feature. - */ plugins { alias(libs.plugins.kotlinMultiplatform) alias(libs.plugins.composeMultiplatform) @@ -17,14 +14,9 @@ version = "1.0.0" kotlin { jvm() - wasmJs { binaries.library() - browser { - testTask { - enabled = false - } - } + browser { testTask { enabled = false } } } sourceSets { @@ -33,12 +25,13 @@ kotlin { implementation(projects.frontend.core.designSystem) implementation(projects.frontend.core.sync) implementation(projects.frontend.core.localDb) - implementation(projects.frontend.core.auth) // Added auth module for AuthTokenManager + implementation(projects.frontend.core.auth) implementation(libs.sqldelight.coroutines) implementation(projects.frontend.core.domain) - implementation(compose.foundation) + // Explizite Compose-Abhängigkeiten zur Vermeidung von Gradle 10 Warnungen implementation(compose.runtime) + implementation(compose.foundation) implementation(compose.material3) implementation(compose.ui) implementation(compose.components.resources) @@ -49,7 +42,7 @@ kotlin { implementation(libs.bundles.compose.common) implementation(libs.koin.core) - implementation(libs.koin.compose) // Added koin.compose for koinInject + implementation(libs.koin.compose) } commonTest.dependencies { @@ -72,6 +65,5 @@ kotlin { wasmJsMain.dependencies { implementation(libs.kotlin.stdlib.wasm.js) } - } } diff --git a/frontend/shells/meldestelle-desktop/build.gradle.kts b/frontend/shells/meldestelle-desktop/build.gradle.kts index dcebf78f..f103a495 100644 --- a/frontend/shells/meldestelle-desktop/build.gradle.kts +++ b/frontend/shells/meldestelle-desktop/build.gradle.kts @@ -91,6 +91,7 @@ kotlin { // Bundles implementation(libs.bundles.kmp.common) implementation(libs.bundles.compose.common) + implementation(libs.logback.classic) } jvmTest.dependencies { @@ -178,6 +179,7 @@ compose.desktop { // JVM-Argumente für die gepackte Anwendung jvmArgs( + "--enable-native-access=ALL-UNNAMED", "-Xms128m", "-Xmx512m", "-Dfile.encoding=UTF-8", diff --git a/frontend/shells/meldestelle-desktop/src/jvmMain/kotlin/at/mocode/frontend/shell/desktop/main.kt b/frontend/shells/meldestelle-desktop/src/jvmMain/kotlin/at/mocode/frontend/shell/desktop/main.kt index b0954514..6f063ad2 100644 --- a/frontend/shells/meldestelle-desktop/src/jvmMain/kotlin/at/mocode/frontend/shell/desktop/main.kt +++ b/frontend/shells/meldestelle-desktop/src/jvmMain/kotlin/at/mocode/frontend/shell/desktop/main.kt @@ -8,7 +8,6 @@ import at.mocode.frontend.core.auth.di.authModule import at.mocode.frontend.core.localdb.AppDatabase import at.mocode.frontend.core.localdb.DatabaseProvider import at.mocode.frontend.core.localdb.localDbModule -import at.mocode.frontend.core.network.NetworkConfig import at.mocode.frontend.core.network.chat.KtorWebSocketServerService import at.mocode.frontend.core.network.discovery.NetworkDiscoveryService import at.mocode.frontend.core.network.networkModule @@ -62,42 +61,32 @@ fun main() = application { desktopModule, ) } - println("[DesktopApp] KOIN initialisiert") - // Base URL Log für schnelle Fehlerdiagnose - println("[Network] baseUrl=${NetworkConfig.baseUrl}") - // Starte Netzwerk-Dienste für den POC + // Datenbank EAGER initialisieren (JVM-safe via runBlocking) val koin = GlobalContext.get() + val dbProvider = koin.get() + val database = runBlocking { dbProvider.createDatabase() } + + loadKoinModules(module { + single { database } + }) + + println("[DesktopApp] KOIN & DB initialisiert") + + // Start POC Netzwerk-Dienste try { val wsServer = koin.get() wsServer.start() val discovery = koin.get() discovery.startDiscovery() - // Im Host-Modus würden wir hier registerService aufrufen. - // Für den POC registrieren wir den lokalen Host-Dienst immer mit dem WS-Port - try { - discovery.registerService(wsServer.getPort()) - println("[DesktopApp] Discovery-Registrierung durchgeführt (Port ${wsServer.getPort()})") - } catch (e: Exception) { - println("[DesktopApp] Discovery-Registrierung fehlgeschlagen: ${e.message}") - } + discovery.registerService(wsServer.getPort()) } catch(e: Exception) { - println("[DesktopApp] POC-Dienste konnten nicht gestartet werden: ${e.message}") + println("[DesktopApp] Netzwerk-Dienste Fehler: %s".format(e.message)) } - // Testdaten für Prototyp laden at.mocode.frontend.shell.desktop.data.Store.seed() } catch (e: Exception) { - println("[DesktopApp] Koin-Warnung: ${e.message}") - } - - try { - val provider = GlobalContext.get().get() - val db = runBlocking { provider.createDatabase() } - loadKoinModules(module { single { db } }) - println("[DesktopApp] Lokale DB bereit") - } catch (e: Exception) { - println("[DesktopApp] DB-Warnung: ${e.message}") + println("[DesktopApp] Startup-Fehler: %s".format(e.message)) } Window( diff --git a/frontend/shells/meldestelle-desktop/src/jvmMain/resources/logback.xml b/frontend/shells/meldestelle-desktop/src/jvmMain/resources/logback.xml new file mode 100644 index 00000000..48f34c8f --- /dev/null +++ b/frontend/shells/meldestelle-desktop/src/jvmMain/resources/logback.xml @@ -0,0 +1,16 @@ + + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + + + + + + diff --git a/gradle.properties b/gradle.properties index e8d30842..1d2900db 100644 --- a/gradle.properties +++ b/gradle.properties @@ -4,31 +4,29 @@ android.nonTransitiveRClass=true # Kotlin Configuration kotlin.code.style=official -# Increased Kotlin Daemon Heap for JS Compilation -kotlin.daemon.jvmargs=-Xmx8g -XX:+UseParallelGC -XX:MaxMetaspaceSize=2g +# Increased Kotlin Daemon Heap for JS Compilation + JDK 25 Warning Suppression +kotlin.daemon.jvmargs=-Xmx8g -XX:+UseParallelGC -XX:MaxMetaspaceSize=2g --add-opens=java.base/java.util=ALL-UNNAMED --add-opens=java.base/java.lang=ALL-UNNAMED --add-opens=java.base/java.io=ALL-UNNAMED --enable-native-access=ALL-UNNAMED kotlin.js.compiler.sourcemaps=false -# Kotlin Compiler Optimizations (Phase 5) +# Kotlin Compiler Optimizations kotlin.incremental=true kotlin.incremental.multiplatform=true kotlin.incremental.js=true - kotlin.caching.enabled=true kotlin.compiler.execution.strategy=in-process -# kotlin.compiler.preciseCompilationResultsBackup=true kotlin.stdlib.default.dependency=true # Gradle Configuration -# Increased Gradle Daemon Heap -org.gradle.jvmargs=-Xmx8g -Dfile.encoding=UTF-8 -Dkotlin.daemon.jvm.options\="-Xmx6g" -XX:+UseParallelGC -XX:MaxMetaspaceSize=2g -XX:+HeapDumpOnOutOfMemoryError -Xshare:off -Djava.awt.headless=true +# Optimized for JDK 25: Added --add-opens and --enable-native-access for compiler tools +org.gradle.jvmargs=-Xmx8g -Dfile.encoding=UTF-8 -XX:+UseParallelGC -XX:MaxMetaspaceSize=2g -XX:+HeapDumpOnOutOfMemoryError -Xshare:off -Djava.awt.headless=true --add-opens=java.base/java.util=ALL-UNNAMED --add-opens=java.base/java.lang=ALL-UNNAMED --add-opens=java.base/java.io=ALL-UNNAMED --add-opens=java.base/java.util.concurrent=ALL-UNNAMED --add-opens=java.base/java.net=ALL-UNNAMED --add-opens=java.base/java.nio=ALL-UNNAMED --enable-native-access=ALL-UNNAMED -Djdk.instrument.traceUsage=false org.gradle.workers.max=8 org.gradle.vfs.watch=true -# Configuration Cache optimieren - TEMPORR DEAKTIVIERT wegen JS-Test Serialisierungsproblemen +# Configuration Cache (JS-Test workaround) org.gradle.configuration-cache=false org.gradle.configuration-cache.problems=warn -# Build Performance verbessern +# Build Performance org.gradle.parallel=true org.gradle.caching=true @@ -46,7 +44,7 @@ org.jetbrains.kotlin.wasm.check.wasm.binary.format=false kotlin.native.ignoreDisabledTargets=true idea.project.settings.delegate.build.run.actions.to.gradle=true -# Enable NPM/Yarn lifecycle scripts for Kotlin/JS (required for sql.js & worker setup) +# NPM/Yarn lifecycle kotlin.js.yarn.ignoreScripts=false org.jetbrains.kotlin.js.yarn.ignoreScripts=false kotlin.js.npm.ignoreScripts=false @@ -56,31 +54,21 @@ org.jetbrains.kotlin.js.npm.ignoreScripts=false org.gradle.logging.level=lifecycle kotlin.build.report.single_file=false -# Compose Experimental Features +# Compose Experimental org.jetbrains.compose.experimental.jscanvas.enabled=true org.jetbrains.compose.experimental.wasm.enabled=true -# Java Toolchain: ensure Gradle auto-downloads a full JDK when needed +# Java Toolchain org.gradle.java.installations.auto-download=true org.gradle.java.installations.auto-detect=true -# Development Environment Support -dev.port.offset=0 -# Set dev.port.offset=100 for second developer -# Set dev.port.offset=200 for the third developer -# ------------------------------------------------------------------ -# Wasm/JS Feature Toggle -# ------------------------------------------------------------------ -# Setze enableWasm=true, um die Web-App zu bauen oder Web-spezifische -# Module zu testen. Default=false spart massiv Zeit beim Desktop-Build. +# Feature Toggles enableWasm=true enableDesktop=true +dev.port.offset=0 -# Dokka Gradle plugin V2 mode (with helpers for V1 compatibility) -# See https://kotl.in/dokka-gradle-migration -# org.jetbrains.dokka.experimental.gradle.pluginMode=V2EnabledWithHelpers +# Dokka V2 org.jetbrains.dokka.experimental.gradle.pluginMode=V2Enabled -# Workaround for Gradle 9 / KMP "Plugin loaded multiple times" error in Docker/CI -# This allows subprojects to re-declare plugins even if they are already on the classpath +# Gradle 9 Workaround kotlin.mpp.allowMultiplePluginDeclarations=true