diff --git a/build.gradle.kts b/build.gradle.kts index e813974c..a434c07d 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -2,8 +2,8 @@ plugins { alias(libs.plugins.kotlin.jvm) apply false alias(libs.plugins.kotlin.multiplatform) apply false - alias(libs.plugins.compose.multiplatform) apply false - alias(libs.plugins.compose.compiler) apply false + alias(libs.plugins.composeMultiplatform) apply false + alias(libs.plugins.composeCompiler) apply false alias(libs.plugins.spring.boot) apply false alias(libs.plugins.spring.dependencyManagement) apply false } @@ -28,6 +28,14 @@ subprojects { // The agent configuration was causing Task.project access at execution time } + // Erzwinge eine stabile Version von kotlinx-serialization-json für alle Konfigurationen, + // um Auflösungsfehler (z.B. 1.10.2, nicht verfügbar auf Maven Central) zu vermeiden + configurations.configureEach { + resolutionStrategy { + force("org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.3") + } + } + // Dedicated performance test task per JVM subproject plugins.withId("java") { val javaExt = extensions.getByType() diff --git a/client/build.gradle.kts b/client/build.gradle.kts index 772a7d9e..282e7334 100644 --- a/client/build.gradle.kts +++ b/client/build.gradle.kts @@ -6,8 +6,9 @@ import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl plugins { alias(libs.plugins.kotlin.multiplatform) alias(libs.plugins.kotlin.serialization) - alias(libs.plugins.compose.multiplatform) - alias(libs.plugins.compose.compiler) + alias(libs.plugins.composeMultiplatform) + alias(libs.plugins.composeCompiler) + alias(libs.plugins.composeHotReload) } // Project version configuration @@ -15,126 +16,139 @@ version = "1.0.0" group = "at.mocode" // Build performance optimizations -tasks.withType().configureEach { - compilerOptions { - jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_21) - freeCompilerArgs.addAll( - "-opt-in=kotlin.RequiresOptIn", - "-Xjvm-default=all" // Generate default methods for interfaces (JVM performance) - ) - } -} +//tasks.withType().configureEach { +// compilerOptions { +// jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_21) +// freeCompilerArgs.addAll( +// "-opt-in=kotlin.RequiresOptIn", +// "-Xjvm-default=all" // Generate default methods for interfaces (JVM performance) +// ) +// } +//} kotlin { - // Configure JVM toolchain for all JVM targets - jvmToolchain(21) +// // Configure JVM toolchain for all JVM targets +// jvmToolchain(21) +// +// // Global compiler options for all targets +// compilerOptions { +// freeCompilerArgs.add("-Xexpect-actual-classes") +// } - // Global compiler options for all targets - compilerOptions { - freeCompilerArgs.add("-Xexpect-actual-classes") - } +// jvm { +// compilations.all { +// compileTaskProvider.configure { +// compilerOptions { +// jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_21) +// freeCompilerArgs.addAll( +// "-Xjsr305=strict", +// "-Xcontext-parameters" +// ) +// } +// } +// } +// } - jvm { - compilations.all { - compileTaskProvider.configure { - compilerOptions { - jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_21) - freeCompilerArgs.addAll( - "-Xjsr305=strict", - "-Xcontext-parameters" - ) - } - } - } - } +// js(IR) { +// browser { +// commonWebpackConfig { +// outputFileName = "meldestelle-client.js" +// cssSupport { +// enabled.set(true) +// } +// // Webpack performance optimizations for smaller bundles +// devServer?.apply { +// open = false +// port = 8080 +// } +// } +// runTask { +// // Development optimizations +// args.add("--mode=development") +// //args.add("--optimization-minimize=false") +// } +// webpackTask { +// // Production optimizations +// args.add("--mode=production") +// args.add("--optimization-minimize") +// } +// testTask { +// // Disable browser tests due to ChromeHeadless permission issues +// enabled = false +// } +// } +// +// // Use Node.js for testing instead of browser +// nodejs { +// testTask { +// useMocha { +// timeout = "10s" +// } +// } +// } +// +// binaries.executable() +// } - js(IR) { - browser { - commonWebpackConfig { - outputFileName = "meldestelle-client.js" - cssSupport { - enabled.set(true) - } - // Webpack performance optimizations for smaller bundles - devServer?.apply { - open = false - port = 8080 - } - } - runTask { - // Development optimizations - args.add("--mode=development") - //args.add("--optimization-minimize=false") - } - webpackTask { - // Production optimizations - args.add("--mode=production") - args.add("--optimization-minimize") - } - testTask { - // Disable browser tests due to ChromeHeadless permission issues - enabled = false - } - } +// wasmJs { +// browser { +// commonWebpackConfig { +// outputFileName = "meldestelle-wasm.js" +// cssSupport { +// enabled.set(true) +// } +// // WASM-specific webpack optimizations handled by webpack.config.d files +// devServer?.apply { +// open = false +// port = 8080 +// } +// } +// runTask { +// // Development optimizations for WASM +// args.add("--mode=development") +// //args.add("--optimization-minimize=false") +// // Dev server settings handled by webpack.config.d/dev-server.js +// } +// webpackTask { +// // Production optimizations for WASM +// args.add("--mode=production") +// args.add("--optimization-minimize") +// } +// testTask { +// // Disable WASM browser tests due to environment issues +// enabled = false +// } +// } +// +// // WASM-specific compiler optimizations for smaller bundles +// compilations.all { +// compileTaskProvider.configure { +// compilerOptions { +// freeCompilerArgs.addAll( +// "-Xwasm-use-new-exception-proposal", +// "-Xwasm-debugger-custom-formatters", +// "-Xwasm-enable-array-range-checks", +// "-Xwasm-generate-wat=false", +// "-opt-in=kotlin.ExperimentalStdlibApi", +// "-opt-in=kotlin.js.ExperimentalJsExport" +// ) +// } +// } +// } +// +// binaries.executable() +// } - // Use Node.js for testing instead of browser - nodejs { - testTask { - useMocha { - timeout = "10s" - } - } - } + jvm() + js { + browser() binaries.executable() } + @OptIn(ExperimentalWasmDsl::class) wasmJs { - browser { - commonWebpackConfig { - outputFileName = "meldestelle-wasm.js" - cssSupport { - enabled.set(true) - } - // WASM-specific webpack optimizations handled by webpack.config.d files - devServer?.apply { - open = false - port = 8080 - } - } - runTask { - // Development optimizations for WASM - args.add("--mode=development") - //args.add("--optimization-minimize=false") - // Dev server settings handled by webpack.config.d/dev-server.js - } - webpackTask { - // Production optimizations for WASM - args.add("--mode=production") - args.add("--optimization-minimize") - } - testTask { - // Disable WASM browser tests due to environment issues - enabled = false - } - } - - // WASM-specific compiler optimizations for smaller bundles - compilations.all { - compileTaskProvider.configure { - compilerOptions { - freeCompilerArgs.addAll( - "-Xwasm-use-new-exception-proposal", - "-Xwasm-debugger-custom-formatters", - "-Xwasm-enable-array-range-checks", - "-Xwasm-generate-wat=false", - "-opt-in=kotlin.ExperimentalStdlibApi", - "-opt-in=kotlin.js.ExperimentalJsExport" - ) - } - } - } - + browser() binaries.executable() } @@ -149,6 +163,10 @@ kotlin { // UiToolingPreview nur für Development, nicht für Production WASM // implementation(compose.components.uiToolingPreview) + implementation(compose.components.uiToolingPreview) + implementation(libs.androidx.lifecycle.viewmodelCompose) + implementation(libs.androidx.lifecycle.runtimeCompose) + // HTTP client dependencies for ping-service - optimiert implementation(libs.ktor.client.core) implementation(libs.ktor.client.contentNegotiation) @@ -161,6 +179,7 @@ kotlin { } jvmMain.dependencies { implementation(compose.desktop.currentOs) + implementation(libs.kotlinx.coroutines.swing) implementation(libs.ktor.client.cio) } jsMain.dependencies { @@ -180,29 +199,29 @@ kotlin { // Exclude Skiko runtime files from jsTest processed resources // to prevent overwriting logs during test packaging. -@Suppress("UNUSED_VARIABLE") -val configureJsTestResources = run { - // Configure only if the task exists (JS target present) - tasks.matching { it.name == "jsTestProcessResources" && it is Copy }.configureEach { - (this as Copy).exclude("skiko.*", "skikod8.mjs") - } -} - -// Also apply the same exclusion for WASM JS test resources, if present -@Suppress("UNUSED_VARIABLE") -val configureWasmJsTestResources = run { - tasks.matching { it.name == "wasmJsTestProcessResources" && it is Copy }.configureEach { - (this as Copy).exclude("skiko.*", "skikod8.mjs") - } -} - -// Ensure Kotlin/JS generated Sync tasks do not overwrite duplicates noisily -@Suppress("UNUSED_VARIABLE") -val configureJsCompileSync = run { - tasks.matching { it.name.endsWith("CompileSync") && it is Sync }.configureEach { - (this as Sync).duplicatesStrategy = DuplicatesStrategy.EXCLUDE - } -} +//@Suppress("UNUSED_VARIABLE") +//val configureJsTestResources = run { +// // Configure only if the task exists (JS target present) +// tasks.matching { it.name == "jsTestProcessResources" && it is Copy }.configureEach { +// (this as Copy).exclude("skiko.*", "skikod8.mjs") +// } +//} +// +//// Also apply the same exclusion for WASM JS test resources, if present +//@Suppress("UNUSED_VARIABLE") +//val configureWasmJsTestResources = run { +// tasks.matching { it.name == "wasmJsTestProcessResources" && it is Copy }.configureEach { +// (this as Copy).exclude("skiko.*", "skikod8.mjs") +// } +//} +// +//// Ensure Kotlin/JS generated Sync tasks do not overwrite duplicates noisily +//@Suppress("UNUSED_VARIABLE") +//val configureJsCompileSync = run { +// tasks.matching { it.name.endsWith("CompileSync") && it is Sync }.configureEach { +// (this as Sync).duplicatesStrategy = DuplicatesStrategy.EXCLUDE +// } +//} compose.desktop { application { diff --git a/client/src/commonMain/resources/index.html b/client/src/commonMain/resources/index.html index b29bad57..11409aa4 100644 --- a/client/src/commonMain/resources/index.html +++ b/client/src/commonMain/resources/index.html @@ -5,6 +5,7 @@ Meldestelle + diff --git a/client/src/jsMain/kotlin/at/mocode/main.kt b/client/src/jsMain/kotlin/at/mocode/main.kt index b92bf62a..df43d6cd 100644 --- a/client/src/jsMain/kotlin/at/mocode/main.kt +++ b/client/src/jsMain/kotlin/at/mocode/main.kt @@ -1,11 +1,11 @@ package at.mocode import androidx.compose.ui.ExperimentalComposeUiApi -import androidx.compose.ui.window.CanvasBasedWindow +import androidx.compose.ui.window.ComposeViewport @OptIn(ExperimentalComposeUiApi::class) fun main() { - CanvasBasedWindow(canvasElementId = "ComposeTarget") { + ComposeViewport("ComposeTarget") { App() } } diff --git a/client/src/wasmJsMain/kotlin/at/mocode/main.kt b/client/src/wasmJsMain/kotlin/at/mocode/main.kt index b92bf62a..df43d6cd 100644 --- a/client/src/wasmJsMain/kotlin/at/mocode/main.kt +++ b/client/src/wasmJsMain/kotlin/at/mocode/main.kt @@ -1,11 +1,11 @@ package at.mocode import androidx.compose.ui.ExperimentalComposeUiApi -import androidx.compose.ui.window.CanvasBasedWindow +import androidx.compose.ui.window.ComposeViewport @OptIn(ExperimentalComposeUiApi::class) fun main() { - CanvasBasedWindow(canvasElementId = "ComposeTarget") { + ComposeViewport("ComposeTarget") { App() } } diff --git a/client/webpack.config.d/bundle-analyzer.js b/client/webpack.config.d/bundle-analyzer.js deleted file mode 100644 index 85ad3350..00000000 --- a/client/webpack.config.d/bundle-analyzer.js +++ /dev/null @@ -1,122 +0,0 @@ -// Bundle Analyzer Configuration for WASM Bundle Size Monitoring -// Helps identify which parts of the bundle are largest and can be optimized - -// Enable bundle analysis based on environment variable -const enableAnalyzer = process.env.ANALYZE_BUNDLE === 'true'; -// Ensure mutable config sections exist to avoid spread on undefined -config.plugins = config.plugins || []; -config.resolve = config.resolve || {}; -config.module = config.module || {}; - -if (enableAnalyzer) { - console.log('📊 Bundle analyzer enabled - generating bundle report...'); - - // Simple bundle size logging without external dependencies - const originalEmit = config.plugins.find(plugin => plugin.constructor.name === 'DefinePlugin'); - - // Add a custom plugin to log bundle sizes - config.plugins.push({ - apply: (compiler) => { - compiler.hooks.done.tap('BundleSizeLogger', (stats) => { - const json = stats.toJson({ all: false, assets: true }); - const assets = (json && json.assets) ? json.assets : []; - - console.log('\n📦 WASM Bundle Analysis Report:'); - console.log('====================================='); - - // Sort assets by size (largest first) - const sortedAssets = assets - .filter(asset => !asset.name.endsWith('.map')) - .sort((a, b) => b.size - a.size); - - let totalSize = 0; - sortedAssets.forEach(asset => { - const sizeKB = (asset.size / 1024).toFixed(2); - const sizeMB = (asset.size / (1024 * 1024)).toFixed(2); - totalSize += asset.size; - - console.log(`📄 ${asset.name}:`); - console.log(` Size: ${sizeKB} KB (${sizeMB} MB)`); - - // Identify what type of asset this likely is - if (asset.name.includes('skiko')) { - console.log(' Type: 🎨 Skiko (Compose UI Framework)'); - } else if (asset.name.includes('ktor')) { - console.log(' Type: 🌐 Ktor (HTTP Client)'); - } else if (asset.name.includes('kotlin')) { - console.log(' Type: 📚 Kotlin Standard Library'); - } else if (asset.name.includes('wasm')) { - console.log(' Type: ⚡ WebAssembly Binary'); - } else if (asset.name.includes('meldestelle')) { - console.log(' Type: 🏠 Application Code'); - } else { - console.log(' Type: 📦 Other/Vendor'); - } - console.log(''); - }); - - const totalSizeKB = (totalSize / 1024).toFixed(2); - const totalSizeMB = (totalSize / (1024 * 1024)).toFixed(2); - - console.log(`📊 Total Bundle Size: ${totalSizeKB} KB (${totalSizeMB} MB)`); - console.log('====================================='); - - // Provide optimization recommendations - const wasmAsset = sortedAssets.find(asset => asset.name.includes('.wasm')); - const jsAsset = sortedAssets.find(asset => asset.name.includes('meldestelle-wasm.js')); - - if (wasmAsset && jsAsset) { - const wasmSizeMB = (wasmAsset.size / (1024 * 1024)).toFixed(2); - const jsSizeKB = (jsAsset.size / 1024).toFixed(2); - - console.log('\n💡 Optimization Recommendations:'); - console.log('====================================='); - - if (wasmAsset.size > 5 * 1024 * 1024) { // > 5MB - console.log(`⚠️ WASM binary is large (${wasmSizeMB}MB). Consider:`); - console.log(' - Reducing Compose UI components'); - console.log(' - Lazy loading features'); - console.log(' - Tree-shaking unused dependencies'); - } - - if (jsAsset.size > 500 * 1024) { // > 500KB - console.log(`⚠️ JS bundle is large (${jsSizeKB}KB). Consider:`); - console.log(' - Code splitting'); - console.log(' - Dynamic imports'); - console.log(' - Removing unused imports'); - } - - if (sortedAssets.length > 10) { - console.log('✅ Good chunk splitting - multiple small files for better caching'); - } - } - - console.log('\n🎯 To analyze specific chunks, set ANALYZE_BUNDLE=true and rebuild'); - console.log('=====================================\n'); - }); - } - }); -} - -// Additional tree-shaking optimizations -config.resolve = { - ...(config.resolve || {}), - // Prioritize ES6 modules for better tree-shaking - mainFields: ['module', 'browser', 'main'], - // Add extensions for better resolution - extensions: ['.js', '.mjs', '.wasm', '.json'] -}; - -// Mark packages as side-effect-free for better tree-shaking -config.module = { - ...(config.module || {}), - rules: [ - ...(config.module && config.module.rules ? config.module.rules : []), - { - // Mark Kotlin-generated code as side-effect-free where possible - test: /\.js$/, - include: /kotlin/, - sideEffects: false - } - ] -}; diff --git a/client/webpack.config.d/dev-server.js b/client/webpack.config.d/dev-server.js deleted file mode 100644 index 55b2e3eb..00000000 --- a/client/webpack.config.d/dev-server.js +++ /dev/null @@ -1,48 +0,0 @@ -// Development server configuration with API proxy -// This forwards API requests from webpack-dev-server to the gateway -const path = require('path'); - -if (config.mode !== 'production') { - config.devServer = { - ...config.devServer, - - // Proxy API requests to the gateway - using modern object syntax - proxy: { - '/api/**': { - target: 'http://localhost:8081', - changeOrigin: true, - secure: false, - logLevel: 'debug', - pathRewrite: { - '^/api': '/api' // Keep the /api prefix for gateway routing - } - } - }, - - // Disable all caches as requested in previous issue - headers: { - 'Cache-Control': 'no-cache, no-store, must-revalidate', - 'Pragma': 'no-cache', - 'Expires': '0' - }, - - // Development middleware settings - devMiddleware: { - writeToDisk: false, - stats: 'minimal' - }, - - // Static files configuration - /* static: { - directory: path.resolve(__dirname, '../../build/dist/wasmJs/developmentExecutable'), - serveIndex: true, - watch: true - },*/ - - // CORS settings for development - allowedHosts: 'all', - historyApiFallback: true, - hot: true, - liveReload: true - }; -} diff --git a/client/webpack.config.d/wasm-optimization.js b/client/webpack.config.d/wasm-optimization.js deleted file mode 100644 index 944edb9c..00000000 --- a/client/webpack.config.d/wasm-optimization.js +++ /dev/null @@ -1,122 +0,0 @@ -// WASM Bundle Size Optimization Configuration -// Advanced Webpack configuration for smaller WASM bundles -//const path = require('path'); - -// Bundle size optimization configuration -config.optimization = { - ...(config.optimization || {}), - // Enable aggressive tree shaking - usedExports: true, - sideEffects: true, - - // Split chunks for better caching and smaller initial bundle - splitChunks: { - chunks: 'all', - cacheGroups: { - // Separate Skiko (Compose UI) into its own chunk - skiko: { - test: /[\\/]skiko[\\/]/, - name: 'skiko', - chunks: 'all', - priority: 30, - reuseExistingChunk: true, - enforce: true - }, - // Separate Ktor client into its own chunk - ktor: { - test: /[\\/]ktor[\\/]/, - name: 'ktor', - chunks: 'all', - priority: 20, - reuseExistingChunk: true - }, - // Separate Kotlin stdlib into its own chunk - kotlinStdlib: { - test: /[\\/]kotlin[\\/]/, - name: 'kotlin-stdlib', - chunks: 'all', - priority: 15, - reuseExistingChunk: true - }, - // Default vendor chunk for remaining dependencies - vendor: { - test: /[\\/]node_modules[\\/]/, - name: 'vendors', - chunks: 'all', - priority: 10, - reuseExistingChunk: true - }, - // Application code chunk - default: { - name: 'app', - minChunks: 2, - priority: 5, - reuseExistingChunk: true - } - } - }, - - // Minimize bundle size - conditional based on mode - minimize: config.mode === 'production' - // Note: minimizer is automatically configured by Kotlin/JS -}; - -// Performance optimization -config.performance = { - ...(config.performance || {}), - // Realistic hint limits for WASM bundles (which are naturally larger) - maxAssetSize: 20000000, // 20MB for individual assets (WASM files can be large) - maxEntrypointSize: 5000000, // 5MB for entrypoints - hints: 'warning' // Show warnings but don't fail the build -}; - -// Resolve optimization for faster builds -config.resolve = { - ...(config.resolve || {}), - // Skip looking in these directories to speed up resolution - modules: ['node_modules'], - // Cache module resolution - cache: true -}; - -// Module optimization -config.module = { - ...(config.module || {}), - // Disable parsing for known pre-built modules - noParse: [ - /kotlin\.js$/, - /kotlinx-.*\.js$/ - ] -}; - -// Development vs Production optimizations -if (config.mode === 'production') { - // Production-specific optimizations - config.output = { - ...(config.output || {}), - // Use conditional filename to match HTML template expectations for main chunk only - filename: (chunkData) => { - return chunkData.chunk.name === 'main' ? 'meldestelle-wasm.js' : '[name].[contenthash:8].js'; - }, - chunkFilename: '[name].[contenthash:8].chunk.js' - }; - - // Additional production optimizations - config.optimization = { - ...(config.optimization || {}), - // Enable module concatenation (scope hoisting) - concatenateModules: true, - // Remove empty chunks - removeEmptyChunks: true, - // Merge duplicate chunks - mergeDuplicateChunks: true - }; -} else { - // Development optimizations for faster builds - config.cache = { - type: 'filesystem', - buildDependencies: { - config: [__filename] - } - }; -} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 72aa0b1f..54861ee6 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -9,6 +9,7 @@ agp = "8.1.4" # --- Kotlin Ecosystem --- kotlin = "2.2.10" kotlinx = "1.9.0" +kotlinxSerializationJson = "1.7.3" kotlinxDatetime = "0.7.1" kotlinLogging = "7.0.3" @@ -19,10 +20,12 @@ springDependencyManagement = "1.1.7" springdoc = "2.8.11" # --- Ktor (API Layer & Client) --- -ktor = "3.2.3" +ktor = "3.3.0" # --- Compose UI --- -composeMultiplatform = "1.8.2" +androidx-lifecycle = "2.9.4" +composeHotReload = "1.0.0-beta07" +composeMultiplatform = "1.9.0" # --- Database & Persistence --- @@ -78,7 +81,7 @@ kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotl kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinx" } kotlinx-coroutines-swing = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-swing" } # Version from BOM kotlinx-coroutines-reactor = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-reactor" } # Version from BOM -kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinx" } +kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinxSerializationJson" } kotlinx-datetime = { module = "org.jetbrains.kotlinx:kotlinx-datetime", version.ref = "kotlinxDatetime" } kotlinx-coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "kotlinx" } @@ -98,6 +101,7 @@ ktor-server-metrics-micrometer = { module = "io.ktor:ktor-server-metrics-microme ktor-server-openapi = { module = "io.ktor:ktor-server-openapi", version.ref = "ktor" } ktor-server-swagger = { module = "io.ktor:ktor-server-swagger", version.ref = "ktor" } ktor-server-tests = { module = "io.ktor:ktor-server-tests-jvm", version.ref = "ktor" } +ktor-server-testHost = { module = "io.ktor:ktor-server-test-host-jvm", version.ref = "ktor" } # --- Ktor Client --- ktor-client-core = { module = "io.ktor:ktor-client-core", version.ref = "ktor" } @@ -171,6 +175,9 @@ jackson-datatype-jsr310 = { module = "com.fasterxml.jackson.datatype:jackson-dat jakarta-annotation-api = { module = "jakarta.annotation:jakarta.annotation-api", version.ref = "jakartaAnnotation" } # --- Compose UI --- +androidx-lifecycle-viewmodelCompose = { module = "org.jetbrains.androidx.lifecycle:lifecycle-viewmodel-compose", version.ref = "androidx-lifecycle" } +androidx-lifecycle-runtimeCompose = { module = "org.jetbrains.androidx.lifecycle:lifecycle-runtime-compose", version.ref = "androidx-lifecycle" } + compose-runtime = { module = "org.jetbrains.compose.runtime:runtime", version.ref = "composeMultiplatform" } compose-foundation = { module = "org.jetbrains.compose.foundation:foundation", version.ref = "composeMultiplatform" } compose-material3 = { module = "org.jetbrains.compose.material3:material3", version.ref = "composeMultiplatform" } @@ -288,7 +295,10 @@ kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", versi kotlin-jpa = { id = "org.jetbrains.kotlin.plugin.jpa", version.ref = "kotlin" } kotlin-spring = { id = "org.jetbrains.kotlin.plugin.spring", version.ref = "kotlin" } ktor = { id = "io.ktor.plugin", version.ref = "ktor" } -compose-multiplatform = { id = "org.jetbrains.compose", version.ref = "composeMultiplatform" } -compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } + +composeHotReload = { id = "org.jetbrains.compose.hot-reload", version.ref = "composeHotReload" } +composeMultiplatform = { id = "org.jetbrains.compose", version.ref = "composeMultiplatform" } +composeCompiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } + spring-boot = { id = "org.springframework.boot", version.ref = "springBoot" } spring-dependencyManagement = { id = "io.spring.dependency-management", version.ref = "springDependencyManagement" }