feat(desktop): Integrate KCEF WebView with Background Download#505
feat(desktop): Integrate KCEF WebView with Background Download#505
Conversation
- Create IdeaChartData.kt with chart data models (ChartConfig, ChartType, etc.) - Create IdeaChartParser.kt using YamlUtils for parsing ChartAgent YAML format - Create IdeaChartRenderer.kt with Jewel-themed chart rendering using Compose Canvas (ComposeCharts library cannot be used due to ClassLoader conflicts with IntelliJ) - Add 'chart', 'graph' case to IdeaSketchRenderer.RenderResponse - Update IdeaAgentSketchBlockBubble to use IdeaChartRenderer for chart content - Update IdeaTimelineItemView to pass project parameter to AgentSketchBlockBubble Supports Pie, Line, Column, and Row chart types with legend display.
…mpact progress bar - Add KCEF installation to ~/.autodev/kcef-bundle directory - Implement real-time download progress tracking with KCEF API - Add compact progress bar at bottom of window (minimally intrusive) - Support manual KCEF installation for users - Add ProGuard rules for KCEF Changes: - ConfigManager: Add getKcefInstallDir() method for all platforms - KcefManager: Handle KCEF initialization and lifecycle - KcefProgressBar: Display download progress with compact UI - DesktopAutoDevApp: Integrate KCEF initialization on startup - Main.kt: Use DesktopAutoDevApp wrapper Progress bar features: - Real-time download progress (0-100%) - Non-blocking background download - Compact single-line layout - User dismissible (download continues) - Automatic hide on completion KCEF downloads ~80-150MB on first run and installs to user home directory.
WalkthroughAdds a cross-platform ConfigManager API for KCEF install directory, a WASM/JS BrowserStorage-backed ConfigManager, chart data/parser/renderer for sketch items, and desktop KCEF lifecycle management (manager, progress UI, and DesktopAutoDevApp wrapper). Also updates build and ProGuard rules and swaps desktop entrypoint to use DesktopAutoDevApp. Changes
Sequence Diagram(s)sequenceDiagram
participant UI as DesktopAutoDevApp
participant KM as KcefManager
participant FS as FileSystem
participant KCEF as KCEF
participant UIProgress as KcefProgressBar
UI->>KM: initialize(onError,onRestart)
activate KM
KM->>KM: check _initializing guard
KM->>FS: ensure installDir exists
alt dir missing
FS-->>KM: created
end
KM->>KM: set state -> Initializing
KM->>KCEF: KCEF.init(installDir, callbacks)
activate KCEF
Note over KCEF: download/extract
KCEF->>KM: onProgress(p)
KM->>UIProgress: update progress UI
KCEF->>KM: onInitSuccess / onRestartRequired / onError
alt success
KM->>KM: set state -> Initialized
else restart required
KM->>KM: set state -> RestartRequired
else error
KM->>KM: set state -> Error(exception)
end
deactivate KCEF
deactivate KM
sequenceDiagram
participant Sketch as IdeaSketchRenderer
participant Renderer as IdeaChartRenderer
participant Parser as IdeaChartParser
participant Canvas as ComposeCanvas
Sketch->>Renderer: request render(chartCode)
activate Renderer
Renderer->>Parser: parse(chartCode)
alt parsed -> ChartConfig
Parser-->>Renderer: ChartConfig
Renderer->>Renderer: select chart kind
alt Pie
Renderer->>Canvas: draw pie segments
else Line
Renderer->>Canvas: draw lines/dots
else Column/Row
Renderer->>Canvas: draw bars
end
Renderer->>Renderer: render legend
else parse failed
Parser-->>Renderer: null
Renderer->>Renderer: render fallback code block
end
deactivate Renderer
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes
Possibly related PRs
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 4
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
mpp-ui/proguard-rules.pro (1)
98-100: Critical: "KEEP EVERYTHING" rule defeats the entire purpose of ProGuard.Lines 99-100 disable all ProGuard functionality with
-keep class * { *; }and-dontwarn **, which:
- Makes all specific rules above (lines 1-94) completely redundant
- Defeats code shrinking, optimization, and obfuscation
- Significantly bloats the final application binary
- Removes all benefits of using ProGuard
This should only be used temporarily for debugging reflection/dynamic loading issues.
Recommendation: Remove these lines entirely, or move to a separate debug-only ProGuard configuration if needed for troubleshooting:
-# KEEP EVERYTHING - Disable ProGuard Stripping --keep class * { *; } --dontwarn **If you're encountering runtime issues that require keeping everything, those specific cases should be added as targeted rules above rather than disabling ProGuard entirely.
🧹 Nitpick comments (13)
mpp-ui/build.gradle.kts (1)
391-406: Refactor JVM args to avoid duplication.JVM args required for KCEF/WebView are configured in two places:
- Lines 391–406: Inside
compose.desktop.application(for the desktop distribution build)- Lines 727–739: Inside
afterEvaluate(for all JavaExec tasks at runtime)While duplication ensures compatibility across different execution contexts, consider centralizing these into a single reusable Gradle property or extension function to reduce maintenance burden and improve clarity.
Example refactor:
// Near the top of the build script, after plugins block val kcefJvmArgs = listOf( "--add-modules", "java.naming,java.sql", "--add-exports", "java.desktop/sun.awt=ALL-UNNAMED", "--add-exports", "java.desktop/java.awt.peer=ALL-UNNAMED", "--add-opens", "java.desktop/sun.awt=ALL-UNNAMED", "--add-opens", "java.desktop/java.awt.peer=ALL-UNNAMED" ) val macosKcefJvmArgs = listOf( "--add-opens", "java.desktop/sun.lwawt=ALL-UNNAMED", "--add-opens", "java.desktop/sun.lwawt.macosx=ALL-UNNAMED" ) // Then reuse in both locations: // In compose.desktop: jvmArgs += kcefJvmArgs if (System.getProperty("os.name").contains("Mac", ignoreCase = true)) { jvmArgs += macosKcefJvmArgs } // In afterEvaluate: tasks.withType<JavaExec> { jvmArgs(*(kcefJvmArgs.toTypedArray())) if (System.getProperty("os.name").contains("Mac", ignoreCase = true)) { jvmArgs(*(macosKcefJvmArgs.toTypedArray())) } }This centralizes the configuration, making it easier to maintain and modify as KCEF requirements evolve.
Also applies to: 727-739
mpp-ui/proguard-rules.pro (1)
89-89: Minor inconsistency in keep rule pattern.The
-keeprule forSwingDispatcherFactoryis missing the{ *; }suffix used consistently throughout the rest of this file (e.g., lines 52-56, 65, 68, 85, 88, 93). While this may be intentional if you only need to preserve the class name (not its members), consider adding{ *; }for consistency.Apply this diff for consistency:
--keep class kotlinx.coroutines.swing.SwingDispatcherFactory +-keep class kotlinx.coroutines.swing.SwingDispatcherFactory { *; }mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/renderer/sketch/IdeaSketchRenderer.kt (1)
139-147: Consider addingblockIsCompleteguard for streaming consistency.Other renderers like
mermaid(line 127) checkblockIsCompletebefore rendering to avoid partial content issues during streaming. The chart case renders immediately without this check, which could cause visual glitches or parsing errors if chart content is incomplete."chart", "graph" -> { - if (fence.text.isNotBlank()) { + if (fence.text.isNotBlank() && blockIsComplete) { IdeaChartRenderer( chartCode = fence.text, modifier = Modifier.fillMaxWidth() ) Spacer(modifier = Modifier.height(8.dp)) } }mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/renderer/sketch/chart/IdeaChartParser.kt (1)
19-24: Broad exception catch masks specific parsing failures.Catching
Exceptionsilently falls back to simple format parsing, potentially hiding YAML syntax errors or type mismatches that users should know about. Consider catching more specific exceptions or logging the failure.return try { parseYamlWithUtils(trimmed) } catch (e: Exception) { + // Log parsing failure for debugging + // logger.debug("YAML parsing failed, falling back to simple format", e) parseSimpleFormat(trimmed) }mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/renderer/sketch/chart/IdeaChartRenderer.kt (1)
242-249: Use theme color for grid lines instead of hardcoded gray.The grid color uses
Color.Graydirectly. Per coding guidelines, prefer theme-based colors for consistency with light/dark modes.-private fun DrawScope.drawGridLines(chartWidth: Float, chartHeight: Float) { - val gridColor = Color.Gray.copy(alpha = 0.3f) +private fun DrawScope.drawGridLines(chartWidth: Float, chartHeight: Float, gridColor: Color) { val gridCount = 4 for (i in 0..gridCount) { val y = i * chartHeight / gridCount drawLine(gridColor, Offset(0f, y), Offset(chartWidth, y), strokeWidth = 1f) } }Then pass
JewelTheme.globalColors.borders.normal.copy(alpha = 0.3f)from the calling composable context.mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/renderer/sketch/chart/IdeaChartData.kt (2)
33-39: Unused property:curvedEdgesis not implemented in the renderer.The
curvedEdgesproperty defaults totruebutRenderLineChartinIdeaChartRenderer.ktalways draws straight line segments. Either implement curved line rendering or remove this property to avoid confusion.data class LineData( val lines: List<LineItem>, val showDots: Boolean = true, - val curvedEdges: Boolean = true, val minValue: Double? = null, val maxValue: Double? = null ) : ChartDataContent()
16-22: Note:widthproperty is currently unused.
ChartConfig.widthis defined but the renderer only usesheight. This is fine if planned for future use, otherwise consider removing to keep the API minimal.mpp-core/src/wasmJsMain/kotlin/cc/unitmesh/config/BrowserStorage.kt (1)
95-111: Consider renaming to avoid potential shadowing.The lowercase
consoleobject name could shadow references to the standard JavaScript console if this package is imported elsewhere. Consider usingWasmConsoleorConsole(capitalized) to follow Kotlin naming conventions and avoid ambiguity.-object console { +object WasmConsole { fun log(message: String) { println("LOG: $message") }mpp-ui/src/jvmMain/kotlin/cc/unitmesh/devins/ui/kcef/KcefProgressBar.kt (2)
127-140: Consider accessibility: touch target may be too small.The 24.dp IconButton with a 14.dp icon might be difficult to tap for some users. Material Design guidelines recommend a minimum touch target of 48.dp.
IconButton( onClick = { dismissed = true onDismiss() }, - modifier = Modifier.size(24.dp) + modifier = Modifier.size(32.dp) ) { Icon( imageVector = Icons.Default.Close, contentDescription = "关闭", tint = MaterialTheme.colorScheme.onSurfaceVariant, - modifier = Modifier.size(14.dp) + modifier = Modifier.size(16.dp) ) }
94-111: Consider extracting UI strings for i18n support.Hardcoded Chinese strings like "正在下载 WebView", "后台进行", and "关闭" could be extracted to a string resources mechanism if internationalization is planned for the desktop app.
mpp-ui/src/jvmMain/kotlin/cc/unitmesh/devins/ui/compose/DesktopAutoDevApp.kt (1)
45-68: Consider usingAutoDevLoggerinstead ofprintln.The codebase uses
AutoDevLoggerfor structured logging (as seen inMain.kt). Replaceprintlncalls withAutoDevLogger.infoor similar for consistency and proper log management.mpp-ui/src/jvmMain/kotlin/cc/unitmesh/devins/ui/kcef/KcefManager.kt (2)
116-140:isInstalled()performs blocking IO on the calling thread.This function accesses the filesystem synchronously and is called from
DesktopAutoDevAppwithin a coroutine. Consider making it a suspend function withwithContext(Dispatchers.IO)to avoid blocking.- fun isInstalled(): Boolean { + suspend fun isInstalled(): Boolean = withContext(Dispatchers.IO) { val installDir = File(ConfigManager.getKcefInstallDir()) val exists = installDir.exists() val files = installDir.listFiles() val fileCount = files?.size ?: 0 // ... logging ... - return hasContent + hasContent }
54-57: Unchecked result frommkdirs().
mkdirs()returnsfalseif directory creation fails (e.g., due to permissions). Consider logging or handling the failure case.if (!installDir.exists()) { - installDir.mkdirs() - println("✅ Created KCEF install directory") + if (installDir.mkdirs()) { + println("✅ Created KCEF install directory") + } else { + println("⚠️ Failed to create KCEF install directory") + } }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (19)
mpp-core/src/androidMain/kotlin/cc/unitmesh/config/ConfigManager.android.kt(1 hunks)mpp-core/src/commonMain/kotlin/cc/unitmesh/config/ConfigManager.kt(1 hunks)mpp-core/src/iosMain/kotlin/cc/unitmesh/config/ConfigManager.ios.kt(1 hunks)mpp-core/src/jsMain/kotlin/cc/unitmesh/config/ConfigManager.js.kt(1 hunks)mpp-core/src/jvmMain/kotlin/cc/unitmesh/config/ConfigManager.jvm.kt(1 hunks)mpp-core/src/wasmJsMain/kotlin/cc/unitmesh/config/BrowserStorage.kt(1 hunks)mpp-core/src/wasmJsMain/kotlin/cc/unitmesh/config/ConfigManager.wasmJs.kt(1 hunks)mpp-idea/build.gradle.kts(3 hunks)mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/components/timeline/IdeaTimelineContent.kt(3 hunks)mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/renderer/sketch/IdeaSketchRenderer.kt(2 hunks)mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/renderer/sketch/chart/IdeaChartData.kt(1 hunks)mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/renderer/sketch/chart/IdeaChartParser.kt(1 hunks)mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/renderer/sketch/chart/IdeaChartRenderer.kt(1 hunks)mpp-ui/build.gradle.kts(2 hunks)mpp-ui/proguard-rules.pro(1 hunks)mpp-ui/src/jvmMain/kotlin/cc/unitmesh/devins/ui/Main.kt(2 hunks)mpp-ui/src/jvmMain/kotlin/cc/unitmesh/devins/ui/compose/DesktopAutoDevApp.kt(1 hunks)mpp-ui/src/jvmMain/kotlin/cc/unitmesh/devins/ui/kcef/KcefManager.kt(1 hunks)mpp-ui/src/jvmMain/kotlin/cc/unitmesh/devins/ui/kcef/KcefProgressBar.kt(1 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.kt
📄 CodeRabbit inference engine (AGENTS.md)
**/*.kt: Useexpect/actualpattern for platform-specific code (for example:Platformimplementations)
Use concrete classes (not interfaces) andPromise(notFlow) for @JsExport annotations
Avoid emoji and UTF-8 characters in WASM code
When modifyingCodingAgentRenderer, update ALL implementations:DefaultCodingAgentRenderer,ComposeRenderer,JewelRenderer,ServerSideRenderer, andJsRendererAdapterin Kotlin
When modifyingCodingAgentRenderer, update JVM CLI implementations:CodingCliRendererandConsoleRenderer
UseAutoDevColorsorMaterialTheme.colorSchemefor Compose styling
Files:
mpp-core/src/jsMain/kotlin/cc/unitmesh/config/ConfigManager.js.ktmpp-core/src/androidMain/kotlin/cc/unitmesh/config/ConfigManager.android.ktmpp-core/src/jvmMain/kotlin/cc/unitmesh/config/ConfigManager.jvm.ktmpp-core/src/wasmJsMain/kotlin/cc/unitmesh/config/BrowserStorage.ktmpp-core/src/iosMain/kotlin/cc/unitmesh/config/ConfigManager.ios.ktmpp-core/src/commonMain/kotlin/cc/unitmesh/config/ConfigManager.ktmpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/renderer/sketch/IdeaSketchRenderer.ktmpp-ui/src/jvmMain/kotlin/cc/unitmesh/devins/ui/kcef/KcefManager.ktmpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/renderer/sketch/chart/IdeaChartParser.ktmpp-ui/src/jvmMain/kotlin/cc/unitmesh/devins/ui/kcef/KcefProgressBar.ktmpp-ui/src/jvmMain/kotlin/cc/unitmesh/devins/ui/compose/DesktopAutoDevApp.ktmpp-ui/src/jvmMain/kotlin/cc/unitmesh/devins/ui/Main.ktmpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/renderer/sketch/chart/IdeaChartRenderer.ktmpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/renderer/sketch/chart/IdeaChartData.ktmpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/components/timeline/IdeaTimelineContent.ktmpp-core/src/wasmJsMain/kotlin/cc/unitmesh/config/ConfigManager.wasmJs.kt
**/*.{kt,ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
Never use hardcoded colors - always use design tokens from the design system
Files:
mpp-core/src/jsMain/kotlin/cc/unitmesh/config/ConfigManager.js.ktmpp-core/src/androidMain/kotlin/cc/unitmesh/config/ConfigManager.android.ktmpp-core/src/jvmMain/kotlin/cc/unitmesh/config/ConfigManager.jvm.ktmpp-core/src/wasmJsMain/kotlin/cc/unitmesh/config/BrowserStorage.ktmpp-core/src/iosMain/kotlin/cc/unitmesh/config/ConfigManager.ios.ktmpp-core/src/commonMain/kotlin/cc/unitmesh/config/ConfigManager.ktmpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/renderer/sketch/IdeaSketchRenderer.ktmpp-ui/src/jvmMain/kotlin/cc/unitmesh/devins/ui/kcef/KcefManager.ktmpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/renderer/sketch/chart/IdeaChartParser.ktmpp-ui/src/jvmMain/kotlin/cc/unitmesh/devins/ui/kcef/KcefProgressBar.ktmpp-ui/src/jvmMain/kotlin/cc/unitmesh/devins/ui/compose/DesktopAutoDevApp.ktmpp-ui/src/jvmMain/kotlin/cc/unitmesh/devins/ui/Main.ktmpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/renderer/sketch/chart/IdeaChartRenderer.ktmpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/renderer/sketch/chart/IdeaChartData.ktmpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/components/timeline/IdeaTimelineContent.ktmpp-core/src/wasmJsMain/kotlin/cc/unitmesh/config/ConfigManager.wasmJs.kt
🧠 Learnings (6)
📚 Learning: 2025-12-07T08:40:10.850Z
Learnt from: CR
Repo: phodal/auto-dev PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-07T08:40:10.850Z
Learning: Applies to **/*.kt : When modifying `CodingAgentRenderer`, update ALL implementations: `DefaultCodingAgentRenderer`, `ComposeRenderer`, `JewelRenderer`, `ServerSideRenderer`, and `JsRendererAdapter` in Kotlin
Applied to files:
mpp-ui/proguard-rules.prompp-idea/src/main/kotlin/cc/unitmesh/devins/idea/renderer/sketch/IdeaSketchRenderer.ktmpp-idea/build.gradle.ktsmpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/renderer/sketch/chart/IdeaChartRenderer.ktmpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/components/timeline/IdeaTimelineContent.kt
📚 Learning: 2025-12-07T08:40:10.850Z
Learnt from: CR
Repo: phodal/auto-dev PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-07T08:40:10.850Z
Learning: Applies to **/*.kt : Avoid emoji and UTF-8 characters in WASM code
Applied to files:
mpp-ui/proguard-rules.pro
📚 Learning: 2025-12-07T08:40:10.850Z
Learnt from: CR
Repo: phodal/auto-dev PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-07T08:40:10.850Z
Learning: Applies to **/*.kt : Use concrete classes (not interfaces) and `Promise` (not `Flow`) for JsExport annotations
Applied to files:
mpp-ui/proguard-rules.pro
📚 Learning: 2025-12-07T08:40:10.850Z
Learnt from: CR
Repo: phodal/auto-dev PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-07T08:40:10.850Z
Learning: Applies to **/*.kt : When modifying `CodingAgentRenderer`, update JVM CLI implementations: `CodingCliRenderer` and `ConsoleRenderer`
Applied to files:
mpp-ui/proguard-rules.prompp-idea/src/main/kotlin/cc/unitmesh/devins/idea/renderer/sketch/IdeaSketchRenderer.ktmpp-idea/build.gradle.ktsmpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/renderer/sketch/chart/IdeaChartRenderer.ktmpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/components/timeline/IdeaTimelineContent.kt
📚 Learning: 2025-12-07T08:40:10.850Z
Learnt from: CR
Repo: phodal/auto-dev PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-07T08:40:10.850Z
Learning: Applies to **/*.kt : Use `AutoDevColors` or `MaterialTheme.colorScheme` for Compose styling
Applied to files:
mpp-ui/src/jvmMain/kotlin/cc/unitmesh/devins/ui/Main.kt
📚 Learning: 2025-12-07T08:40:10.850Z
Learnt from: CR
Repo: phodal/auto-dev PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-07T08:40:10.850Z
Learning: Applies to mpp-vscode/src/**/*.ts : When modifying `CodingAgentRenderer`, update VSCode implementations: `mpp-vscode/src/bridge/mpp-core.ts` and `mpp-vscode/src/providers/chat-view.ts`
Applied to files:
mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/components/timeline/IdeaTimelineContent.kt
🧬 Code graph analysis (4)
mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/renderer/sketch/IdeaSketchRenderer.kt (1)
mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/renderer/sketch/chart/IdeaChartRenderer.kt (1)
IdeaChartRenderer(31-72)
mpp-ui/src/jvmMain/kotlin/cc/unitmesh/devins/ui/Main.kt (1)
mpp-ui/src/jvmMain/kotlin/cc/unitmesh/devins/ui/compose/DesktopAutoDevApp.kt (1)
DesktopAutoDevApp(20-102)
mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/components/timeline/IdeaTimelineContent.kt (2)
mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/renderer/sketch/chart/IdeaChartRenderer.kt (1)
IdeaChartRenderer(31-72)mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/renderer/sketch/IdeaMermaidRenderer.kt (1)
IdeaMermaidRenderer(32-95)
mpp-core/src/wasmJsMain/kotlin/cc/unitmesh/config/ConfigManager.wasmJs.kt (5)
mpp-core/src/androidMain/kotlin/cc/unitmesh/config/ConfigManager.android.kt (5)
createEmpty(245-247)parseYamlConfig(252-264)toYaml(269-271)load(58-74)save(76-89)mpp-core/src/iosMain/kotlin/cc/unitmesh/config/ConfigManager.ios.kt (4)
createEmpty(275-282)parseYamlConfig(284-297)load(38-60)save(62-93)mpp-core/src/jsMain/kotlin/cc/unitmesh/config/ConfigManager.js.kt (5)
createEmpty(297-299)parseYamlConfig(304-316)toYaml(321-323)load(58-89)save(91-114)mpp-core/src/jvmMain/kotlin/cc/unitmesh/config/ConfigManager.jvm.kt (5)
createEmpty(190-192)parseYamlConfig(197-209)toYaml(211-213)load(29-46)save(48-61)mpp-core/src/commonMain/kotlin/cc/unitmesh/config/ConfigManager.kt (2)
load(32-32)save(37-37)
🔇 Additional comments (25)
mpp-ui/build.gradle.kts (3)
1-10: Verify the purpose ofExperimentalKotlinGradlePluginApiopt-in.The file-level
@file:OptInannotation enables experimental Gradle plugin features. Clarify which feature requires this opt-in and whether it introduces risks for future Gradle/Kotlin version updates.
92-106: Confirm that commenting out thenodejs {}block doesn't impact JS target compilation.The nodejs block is commented with an explanation that the Web UI uses pure TypeScript/React. However, verify that this doesn't break or unintentionally disable the JS IR target compilation (lines 92–106), which is still needed for the Node.js CLI architecture. The
useCommonJs(),binaries.executable(), and compiler options remain, but confirm the intent is to skip only the explicit Node.js CLI configuration, not the entire JS target.
188-188: No action needed — version 2.0.3 is the latest stable release.The version is current as of August 30, 2025, and is the latest stable release published to Maven Central for both the main and wasm-js variants.
mpp-ui/proguard-rules.pro (1)
87-88: LGTM! KCEF and WebView rules are properly structured.The ProGuard keep rules for KCEF and Compose WebView Multiplatform follow the established pattern in this file and appropriately support the WebView integration described in the PR objectives.
Also applies to: 90-90, 92-94
mpp-idea/build.gradle.kts (2)
360-365: These exclusions follow a consistent pattern across all Ktor dependencies and align with the runtime strategy.Lines 360–365 exclude
kotlinx-serialization-json-ioandkotlinx-io-core(and jvm variants) as part of a coherent dependency management approach. Sincektor-serialization-kotlinx-jsonis explicitly included at line 355 (required for AbstractOpenAILLMClient), these transitive exclusions safely defer to IntelliJ's platform-provided libraries. The same exclusion set is uniformly applied across all Ktor dependencies in the file, confirming this is intentional rather than a one-off addition.
395-398: Clarify that mpp-core provides aligned ktor dependencies—exclusion logic is sound.The comment correctly explains why
ktor-serialization-kotlinx-jsonis excluded (mpp-idea provides it explicitly), and whyktor-client-content-negotiationis not excluded (required by AbstractOpenAILLMClient at runtime). However, the assumption that "mpp-core does not transitively bring in ktor-client-core or ktor-client-cio" is inaccurate. mpp-core declares ktor-client-core:3.2.2, ktor-client-cio:3.2.2, and ktor-client-content-negotiation:3.2.2 across its platform-specific source sets. Since all versions are aligned at 3.2.2 between mpp-idea and mpp-core, the exclusion strategy works correctly—only ktor-serialization-kotlinx-json needs exclusion. Consider updating the comment to reflect that mpp-core provides these ktor dependencies and they remain unexcluded because versions match and AbstractOpenAILLMClient requires ktor-client-content-negotiation.mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/components/timeline/IdeaTimelineContent.kt (1)
184-237: LGTM!The fallback renderer handles code display well with line numbers, sensible truncation at 20 lines, and proper use of
JewelThemefor styling.mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/renderer/sketch/chart/IdeaChartParser.kt (1)
47-58: LGTM!The YAML parsing structure with type-specific handlers and null-safe mapping is well-designed. The fallback chain ensures graceful degradation.
mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/renderer/sketch/chart/IdeaChartRenderer.kt (2)
301-349: LGTM!The color utilities correctly handle hex parsing (6/8 digit) and named color mapping. The default color palette is appropriate for data visualization where consistent series colors are expected regardless of theme.
133-177: LGTM!The line chart rendering correctly handles edge cases: single data points are centered, and equal min/max values default to a range of 1.0 to prevent division by zero.
mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/renderer/sketch/chart/IdeaChartData.kt (1)
54-80: LGTM!The data model classes (
PieItem,LineItem,BarGroup,BarValue) are well-designed with sensible optional fields and nullable defaults for colors.mpp-core/src/wasmJsMain/kotlin/cc/unitmesh/config/BrowserStorage.kt (1)
1-89: LGTM! Well-structured localStorage wrapper with proper error handling.The
BrowserStorageobject correctly wraps all localStorage operations with try/catch blocks and provides sensible fallbacks (null for reads, empty list for keys). The externalStorageinterface properly declares the browser localStorage API.mpp-core/src/iosMain/kotlin/cc/unitmesh/config/ConfigManager.ios.kt (1)
253-257: LGTM!The implementation correctly returns an empty string with a clear comment explaining KCEF is not available on iOS. This follows the expect/actual pattern and is consistent with other unsupported platforms.
mpp-core/src/commonMain/kotlin/cc/unitmesh/config/ConfigManager.kt (1)
137-143: LGTM!The expect declaration is well-documented and correctly defines the cross-platform API for retrieving the KCEF installation directory. Using a non-suspend function is appropriate since path computation doesn't require I/O.
mpp-core/src/androidMain/kotlin/cc/unitmesh/config/ConfigManager.android.kt (1)
192-196: LGTM!Consistent with other unsupported platforms, correctly returns an empty string with an explanatory comment.
mpp-core/src/jvmMain/kotlin/cc/unitmesh/config/ConfigManager.jvm.kt (1)
242-246: LGTM!The implementation correctly computes the KCEF installation path under
~/.autodev/kcef-bundle, matching the PR objectives. The function appropriately only returns the path without creating the directory, leaving directory creation to the KCEF initialization logic.mpp-core/src/jsMain/kotlin/cc/unitmesh/config/ConfigManager.js.kt (1)
291-295: LGTM!Correctly returns an empty string with a clear comment, consistent with other non-desktop platforms.
mpp-core/src/wasmJsMain/kotlin/cc/unitmesh/config/ConfigManager.wasmJs.kt (4)
1-27: LGTM! Clean setup with proper logging.The WASM ConfigManager is well-structured with proper imports, a dedicated logger, and consistent JSON configuration. Using BrowserStorage for persistence is the correct approach for browser environments.
28-53: Well-implemented load/save with proper error handling.Using
logger.info/logger.errorfor structured logging is preferable toprintlnused in some other platform implementations. The error handling correctly returns fallbacks on load and rethrows on save failures.
153-156: LGTM!Correctly returns an empty string with an explanatory comment, consistent with other non-desktop platforms.
204-223: No action required. The implementation correctly usesYamlUtilsin the WASM target, and full WASM support is confirmed by thekamllibrary dependency and comprehensive WASM-specific tests that validateloadAs()anddump()operations.mpp-ui/src/jvmMain/kotlin/cc/unitmesh/devins/ui/Main.kt (1)
14-14: LGTM! Clean integration of DesktopAutoDevApp wrapper.The import and usage change correctly integrates the new KCEF-aware wrapper while preserving all existing callback wiring. The parameter signatures match, ensuring backward compatibility.
Also applies to: 115-137
mpp-ui/src/jvmMain/kotlin/cc/unitmesh/devins/ui/kcef/KcefProgressBar.kt (1)
52-56: Good use ofMaterialTheme.colorSchemefor styling.The use of
MaterialTheme.colorScheme.surfaceVariantwith alpha follows the coding guidelines for Compose styling. Based on learnings, the theming approach is correct.mpp-ui/src/jvmMain/kotlin/cc/unitmesh/devins/ui/compose/DesktopAutoDevApp.kt (1)
74-101: LGTM! Clean composition of AutoDevApp with overlay.The Box layout correctly layers the main content with the progress bar overlay, and all parameters are properly forwarded to AutoDevApp.
mpp-ui/src/jvmMain/kotlin/cc/unitmesh/devins/ui/kcef/KcefManager.kt (1)
146-152: LGTM! Well-structured sealed class for state management.The
KcefInitStatesealed class clearly models all possible states with appropriate data (Error includes the exception). Usingdata objectfor stateless variants is idiomatic Kotlin.
| implementation("io.ktor:ktor-client-core:3.2.2") { | ||
| exclude(group = "org.jetbrains.kotlinx", module = "kotlinx-coroutines-core") | ||
| exclude(group = "org.jetbrains.kotlinx", module = "kotlinx-coroutines-core-jvm") | ||
| exclude(group = "org.jetbrains.kotlinx", module = "kotlinx-serialization-json") | ||
| exclude(group = "org.jetbrains.kotlinx", module = "kotlinx-serialization-json-jvm") | ||
| exclude(group = "org.jetbrains.kotlinx", module = "kotlinx-serialization-json-io") | ||
| exclude(group = "org.jetbrains.kotlinx", module = "kotlinx-serialization-json-io-jvm") | ||
| exclude(group = "org.jetbrains.kotlinx", module = "kotlinx-serialization-core") | ||
| exclude(group = "org.jetbrains.kotlinx", module = "kotlinx-serialization-core-jvm") | ||
| exclude(group = "org.jetbrains.kotlinx", module = "kotlinx-io-core") | ||
| exclude(group = "org.jetbrains.kotlinx", module = "kotlinx-io-core-jvm") | ||
| } | ||
| implementation("io.ktor:ktor-client-cio:3.2.2") { | ||
| exclude(group = "org.jetbrains.kotlinx", module = "kotlinx-coroutines-core") | ||
| exclude(group = "org.jetbrains.kotlinx", module = "kotlinx-coroutines-core-jvm") | ||
| exclude(group = "org.jetbrains.kotlinx", module = "kotlinx-serialization-json") | ||
| exclude(group = "org.jetbrains.kotlinx", module = "kotlinx-serialization-json-jvm") | ||
| exclude(group = "org.jetbrains.kotlinx", module = "kotlinx-serialization-json-io") | ||
| exclude(group = "org.jetbrains.kotlinx", module = "kotlinx-serialization-json-io-jvm") | ||
| exclude(group = "org.jetbrains.kotlinx", module = "kotlinx-serialization-core") | ||
| exclude(group = "org.jetbrains.kotlinx", module = "kotlinx-serialization-core-jvm") | ||
| exclude(group = "org.jetbrains.kotlinx", module = "kotlinx-io-core") | ||
| exclude(group = "org.jetbrains.kotlinx", module = "kotlinx-io-core-jvm") | ||
| } | ||
| implementation("io.ktor:ktor-client-content-negotiation:3.2.2") { | ||
| exclude(group = "org.jetbrains.kotlinx", module = "kotlinx-coroutines-core") | ||
| exclude(group = "org.jetbrains.kotlinx", module = "kotlinx-coroutines-core-jvm") | ||
| exclude(group = "org.jetbrains.kotlinx", module = "kotlinx-serialization-json") | ||
| exclude(group = "org.jetbrains.kotlinx", module = "kotlinx-serialization-json-jvm") | ||
| exclude(group = "org.jetbrains.kotlinx", module = "kotlinx-serialization-json-io") | ||
| exclude(group = "org.jetbrains.kotlinx", module = "kotlinx-serialization-json-io-jvm") | ||
| exclude(group = "org.jetbrains.kotlinx", module = "kotlinx-serialization-core") | ||
| exclude(group = "org.jetbrains.kotlinx", module = "kotlinx-serialization-core-jvm") | ||
| exclude(group = "org.jetbrains.kotlinx", module = "kotlinx-io-core") | ||
| exclude(group = "org.jetbrains.kotlinx", module = "kotlinx-io-core-jvm") | ||
| } |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major
Extract repetitive Ktor exclusion patterns into a reusable helper function.
Lines 319–330, 331–342, and 343–354 repeat nearly identical exclusion blocks across three Ktor artifacts. This duplication makes the build script harder to maintain and increases the risk of inconsistencies if the exclusion pattern needs to change in the future.
Extract these exclusions into a Gradle extension function at the top of the dependencies block or as a helper at the file level. For example:
fun <T : ModuleDependency> T.excludeKotlinxForIntelliJ() {
exclude(group = "org.jetbrains.kotlinx", module = "kotlinx-coroutines-core")
exclude(group = "org.jetbrains.kotlinx", module = "kotlinx-coroutines-core-jvm")
exclude(group = "org.jetbrains.kotlinx", module = "kotlinx-serialization-json")
exclude(group = "org.jetbrains.kotlinx", module = "kotlinx-serialization-json-jvm")
exclude(group = "org.jetbrains.kotlinx", module = "kotlinx-serialization-json-io")
exclude(group = "org.jetbrains.kotlinx", module = "kotlinx-serialization-json-io-jvm")
exclude(group = "org.jetbrains.kotlinx", module = "kotlinx-serialization-core")
exclude(group = "org.jetbrains.kotlinx", module = "kotlinx-serialization-core-jvm")
exclude(group = "org.jetbrains.kotlinx", module = "kotlinx-io-core")
exclude(group = "org.jetbrains.kotlinx", module = "kotlinx-io-core-jvm")
}Then simplify each Ktor dependency to:
-implementation("io.ktor:ktor-client-core:3.2.2") {
- exclude(group = "org.jetbrains.kotlinx", module = "kotlinx-coroutines-core")
- // ... (10 more excludes)
-}
+implementation("io.ktor:ktor-client-core:3.2.2") {
+ excludeKotlinxForIntelliJ()
+}This pattern is also used in mpp-idea-core (lines 580–603), so consolidating it would improve clarity across the entire build file.
🤖 Prompt for AI Agents
In mpp-idea/build.gradle.kts around lines 319–354 (and also apply the same
change to mpp-idea-core around lines 580–603), the same long list of kotlinx
exclusions is duplicated for multiple Ktor dependencies; extract that list into
a single helper extension function (defined at file level or at the top of the
dependencies block) that accepts a ModuleDependency receiver and performs every
exclude(...) call, then replace each duplicated exclusion block with a single
invocation of that helper on the dependency; ensure the helper is visible where
dependencies are declared and update all occurrences (including the
mpp-idea-core section) to call the helper so maintenance is centralized.
| "mermaid", "mmd" -> { | ||
| val disposable = parentDisposable ?: Disposer.newDisposable("AgentSketchBlock") | ||
| IdeaMermaidRenderer( | ||
| mermaidCode = item.code, | ||
| project = project, | ||
| isDarkTheme = true, | ||
| parentDisposable = disposable, | ||
| modifier = Modifier.fillMaxWidth() | ||
| ) |
There was a problem hiding this comment.
Resource leak: orphan Disposable is never disposed.
When parentDisposable is null, a new Disposable is created but never registered for cleanup. This leaks resources on every recomposition. The disposable should be tied to the composable's lifecycle using DisposableEffect or require a non-null parentDisposable.
"mermaid", "mmd" -> {
- val disposable = parentDisposable ?: Disposer.newDisposable("AgentSketchBlock")
+ val disposable = parentDisposable
+ ?: return@Column // Skip rendering if no parent disposable available
IdeaMermaidRenderer(
mermaidCode = item.code,
project = project,
isDarkTheme = true,
parentDisposable = disposable,
modifier = Modifier.fillMaxWidth()
)
}Alternatively, manage the disposable lifecycle properly:
val disposable = remember { parentDisposable ?: Disposer.newDisposable("AgentSketchBlock") }
DisposableEffect(Unit) {
onDispose {
if (parentDisposable == null) Disposer.dispose(disposable)
}
}🤖 Prompt for AI Agents
In
mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/components/timeline/IdeaTimelineContent.kt
around lines 162 to 170, a new Disposable is created when parentDisposable is
null but never registered or disposed, leaking resources on recomposition;
instead, ensure the disposable is tied to the composable lifecycle by
remembering the disposable (e.g., using remember { parentDisposable ?:
Disposer.newDisposable(...) }) and disposing it in a DisposableEffect onDispose
(only dispose if parentDisposable was null), or require a non-null
parentDisposable to be passed so no orphan Disposable is created.
| LaunchedEffect(Unit) { | ||
| scope.launch { | ||
| try { | ||
| println("🔍 Checking KCEF installation status...") | ||
| val installed = KcefManager.isInstalled() | ||
| println("📊 KCEF installed: $installed") | ||
|
|
||
| if (!installed) { | ||
| println("📦 KCEF not installed, starting download and initialization...") | ||
| println("⏰ This may take a few minutes on first run (80-150MB download)") | ||
| KcefManager.initialize( | ||
| onError = { error -> | ||
| println("❌ KCEF initialization failed: ${error.message}") | ||
| error.printStackTrace() | ||
| onNotification("WebView 初始化失败", error.message ?: "未知错误") | ||
| }, | ||
| onRestartRequired = { | ||
| println("🔄 KCEF requires restart") | ||
| onNotification("需要重启", "WebView 组件需要重启应用才能生效") | ||
| } | ||
| ) | ||
| println("✅ KCEF initialization request completed") | ||
| } else { | ||
| println("✅ KCEF already installed, skipping download") | ||
| } | ||
| } catch (e: Exception) { | ||
| println("⚠️ KCEF initialization error: ${e.message}") | ||
| e.printStackTrace() | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
Redundant scope.launch inside LaunchedEffect.
LaunchedEffect already provides a coroutine scope. The nested scope.launch detaches the initialization from LaunchedEffect's lifecycle, meaning the coroutine won't be cancelled if the composable leaves composition.
LaunchedEffect(Unit) {
- scope.launch {
- try {
- println("🔍 Checking KCEF installation status...")
- val installed = KcefManager.isInstalled()
- println("📊 KCEF installed: $installed")
-
- if (!installed) {
- println("📦 KCEF not installed, starting download and initialization...")
- println("⏰ This may take a few minutes on first run (80-150MB download)")
- KcefManager.initialize(
- onError = { error ->
- println("❌ KCEF initialization failed: ${error.message}")
- error.printStackTrace()
- onNotification("WebView 初始化失败", error.message ?: "未知错误")
- },
- onRestartRequired = {
- println("🔄 KCEF requires restart")
- onNotification("需要重启", "WebView 组件需要重启应用才能生效")
- }
- )
- println("✅ KCEF initialization request completed")
- } else {
- println("✅ KCEF already installed, skipping download")
- }
- } catch (e: Exception) {
- println("⚠️ KCEF initialization error: ${e.message}")
- e.printStackTrace()
- }
+ try {
+ println("🔍 Checking KCEF installation status...")
+ val installed = KcefManager.isInstalled()
+ println("📊 KCEF installed: $installed")
+
+ if (!installed) {
+ println("📦 KCEF not installed, starting download and initialization...")
+ println("⏰ This may take a few minutes on first run (80-150MB download)")
+ KcefManager.initialize(
+ onError = { error ->
+ println("❌ KCEF initialization failed: ${error.message}")
+ error.printStackTrace()
+ onNotification("WebView 初始化失败", error.message ?: "未知错误")
+ },
+ onRestartRequired = {
+ println("🔄 KCEF requires restart")
+ onNotification("需要重启", "WebView 组件需要重启应用才能生效")
+ }
+ )
+ println("✅ KCEF initialization request completed")
+ } else {
+ println("✅ KCEF already installed, skipping download")
+ }
+ } catch (e: Exception) {
+ println("⚠️ KCEF initialization error: ${e.message}")
+ e.printStackTrace()
}
}Also, consider removing the unused scope variable on line 35 if you apply this change.
| if (_initState.value is KcefInitState.Initialized) { | ||
| println("✅ KCEF already initialized") | ||
| return | ||
| } | ||
|
|
||
| if (_initState.value is KcefInitState.Initializing) { | ||
| println("⏳ KCEF initialization already in progress") | ||
| return | ||
| } | ||
|
|
||
| _initState.value = KcefInitState.Initializing |
There was a problem hiding this comment.
Potential race condition in initialization guard.
The check-then-act pattern between lines 36-46 is not atomic. Two concurrent calls to initialize() could both pass the guards before either updates the state to Initializing, leading to double initialization.
Consider using an atomic compare-and-set pattern:
+ private val initializationLock = Any()
+
suspend fun initialize(
onError: ((Throwable) -> Unit)? = null,
onRestartRequired: (() -> Unit)? = null
) {
- if (_initState.value is KcefInitState.Initialized) {
- println("✅ KCEF already initialized")
- return
- }
-
- if (_initState.value is KcefInitState.Initializing) {
- println("⏳ KCEF initialization already in progress")
- return
- }
-
- _initState.value = KcefInitState.Initializing
+ synchronized(initializationLock) {
+ if (_initState.value is KcefInitState.Initialized) {
+ println("✅ KCEF already initialized")
+ return
+ }
+ if (_initState.value is KcefInitState.Initializing) {
+ println("⏳ KCEF initialization already in progress")
+ return
+ }
+ _initState.value = KcefInitState.Initializing
+ }Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In mpp-ui/src/jvmMain/kotlin/cc/unitmesh/devins/ui/kcef/KcefManager.kt around
lines 36-46 the check-then-act pattern on _initState can race and allow two
concurrent initialize() callers to both proceed; change the guard to use an
atomic compare-and-set or a short critical section so only one caller flips the
state to Initializing. Replace the current if-checks with a single atomic
operation (e.g., use an AtomicReference for state and call
compareAndSet(expected=NotInitialized, update=Initializing), or protect the
check-and-set with a Mutex/synchronized block) and only proceed with
initialization when the CAS/sync succeeds; otherwise return early if the CAS
fails because another caller started initialization.
… package structure
There was a problem hiding this comment.
Pull request overview
This PR integrates KCEF (Kotlin Chromium Embedded Framework) into the Desktop application with background download capabilities and a compact progress UI. It also adds chart rendering support to the IntelliJ IDEA plugin using manual Canvas API implementation.
Key Changes:
- KCEF WebView integration for Desktop with automatic background download (~80-150MB) on first run, stored in
~/.autodev/kcef-bundle - Desktop-specific wrapper component (
DesktopAutoDevApp) to handle KCEF initialization without modifying shared code - Manual chart rendering implementation (pie, line, column, row) for IntelliJ IDEA plugin using Compose Canvas API to avoid ClassLoader conflicts
Reviewed changes
Copilot reviewed 20 out of 20 changed files in this pull request and generated 8 comments.
Show a summary per file
| File | Description |
|---|---|
mpp-ui/src/jvmMain/kotlin/cc/unitmesh/devins/ui/kcef/KcefManager.kt |
Core KCEF lifecycle manager with progress tracking and installation checks |
mpp-ui/src/jvmMain/kotlin/cc/unitmesh/devins/ui/kcef/KcefProgressBar.kt |
Non-blocking, dismissible progress UI for KCEF downloads |
mpp-ui/src/jvmMain/kotlin/cc/unitmesh/devins/ui/compose/DesktopAutoDevApp.kt |
Desktop-specific wrapper for KCEF integration and progress display |
mpp-ui/src/jvmMain/kotlin/cc/unitmesh/devins/ui/Main.kt |
Updated to use DesktopAutoDevApp instead of AutoDevApp |
mpp-ui/proguard-rules.pro |
Added KCEF and WebView keep rules for ProGuard |
mpp-ui/build.gradle.kts |
Disabled nodejs target (commented out) |
mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/renderer/sketch/chart/IdeaChartRenderer.kt |
Manual chart renderer using Canvas API for 4 chart types |
mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/renderer/sketch/chart/IdeaChartParser.kt |
YAML/simple format parser for chart configurations |
mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/renderer/sketch/chart/IdeaChartData.kt |
Chart data model definitions |
mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/renderer/sketch/IdeaSketchRenderer.kt |
Added chart/graph language support to sketch renderer |
mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/components/timeline/IdeaTimelineContent.kt |
Integrated chart and mermaid renderers into timeline items |
mpp-idea/build.gradle.kts |
Changed Ktor dependencies from compileOnly to implementation with extensive exclusions |
mpp-core/src/commonMain/kotlin/cc/unitmesh/config/ConfigManager.kt |
Added getKcefInstallDir() method to interface |
mpp-core/src/jvmMain/kotlin/cc/unitmesh/config/ConfigManager.jvm.kt |
Implemented getKcefInstallDir() returning ~/.autodev/kcef-bundle |
mpp-core/src/jsMain/kotlin/cc/unitmesh/config/ConfigManager.js.kt |
Stub implementation returning empty string |
mpp-core/src/iosMain/kotlin/cc/unitmesh/config/ConfigManager.ios.kt |
Stub implementation returning empty string |
mpp-core/src/androidMain/kotlin/cc/unitmesh/config/ConfigManager.android.kt |
Stub implementation returning empty string |
mpp-core/src/wasmJsMain/kotlin/cc/unitmesh/config/ConfigManager.wasmJs.kt |
Full ConfigManager implementation with getKcefInstallDir() stub |
mpp-core/src/wasmJsMain/kotlin/cc/unitmesh/config/BrowserStorage.kt |
Browser localStorage wrapper for WASM platform |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| @Composable | ||
| fun DesktopAutoDevApp( | ||
| triggerFileChooser: Boolean = false, | ||
| onFileChooserHandled: () -> Unit = {}, | ||
| initialMode: String = "auto", | ||
| showTopBarInContent: Boolean = true, | ||
| initialAgentType: AgentType = AgentType.CODING, | ||
| initialTreeViewVisible: Boolean = false, | ||
| onAgentTypeChanged: (AgentType) -> Unit = {}, | ||
| onTreeViewVisibilityChanged: (Boolean) -> Unit = {}, | ||
| onSidebarVisibilityChanged: (Boolean) -> Unit = {}, | ||
| onWorkspacePathChanged: (String) -> Unit = {}, | ||
| onHasHistoryChanged: (Boolean) -> Unit = {}, | ||
| onNotification: (String, String) -> Unit = { _, _ -> } | ||
| ) { | ||
| val scope = rememberCoroutineScope() | ||
|
|
||
| // KCEF initialization state | ||
| val kcefInitState by KcefManager.initState.collectAsState() | ||
| val kcefDownloadProgress by KcefManager.downloadProgress.collectAsState() | ||
|
|
||
| // Initialize KCEF on first launch (background operation) | ||
| LaunchedEffect(Unit) { | ||
| scope.launch { | ||
| try { | ||
| println("🔍 Checking KCEF installation status...") | ||
| val installed = KcefManager.isInstalled() | ||
| println("📊 KCEF installed: $installed") | ||
|
|
||
| if (!installed) { | ||
| println("📦 KCEF not installed, starting download and initialization...") | ||
| println("⏰ This may take a few minutes on first run (80-150MB download)") | ||
| KcefManager.initialize( | ||
| onError = { error -> | ||
| println("❌ KCEF initialization failed: ${error.message}") | ||
| error.printStackTrace() | ||
| onNotification("WebView 初始化失败", error.message ?: "未知错误") | ||
| }, | ||
| onRestartRequired = { | ||
| println("🔄 KCEF requires restart") | ||
| onNotification("需要重启", "WebView 组件需要重启应用才能生效") | ||
| } | ||
| ) | ||
| println("✅ KCEF initialization request completed") | ||
| } else { | ||
| println("✅ KCEF already installed, skipping download") | ||
| } | ||
| } catch (e: Exception) { | ||
| println("⚠️ KCEF initialization error: ${e.message}") | ||
| e.printStackTrace() | ||
| } | ||
| } | ||
| } | ||
|
|
||
| Box(modifier = Modifier.fillMaxSize()) { | ||
| // Main AutoDevApp content | ||
| AutoDevApp( | ||
| triggerFileChooser = triggerFileChooser, | ||
| onFileChooserHandled = onFileChooserHandled, | ||
| initialMode = initialMode, | ||
| showTopBarInContent = showTopBarInContent, | ||
| initialAgentType = initialAgentType, | ||
| initialTreeViewVisible = initialTreeViewVisible, | ||
| onAgentTypeChanged = onAgentTypeChanged, | ||
| onTreeViewVisibilityChanged = onTreeViewVisibilityChanged, | ||
| onSidebarVisibilityChanged = onSidebarVisibilityChanged, | ||
| onWorkspacePathChanged = onWorkspacePathChanged, | ||
| onHasHistoryChanged = onHasHistoryChanged, | ||
| onNotification = onNotification | ||
| ) | ||
|
|
||
| // KCEF progress bar at the bottom (overlays the main content) | ||
| KcefProgressBar( | ||
| initState = kcefInitState, | ||
| downloadProgress = kcefDownloadProgress, | ||
| modifier = Modifier.align(Alignment.BottomCenter), | ||
| onDismiss = { | ||
| // User dismissed the notification, but download continues in background | ||
| println("🔕 KCEF progress bar dismissed by user") | ||
| } | ||
| ) | ||
| } | ||
| } |
There was a problem hiding this comment.
KCEF resources are never disposed. The KcefManager.dispose() method is defined but never called when the application closes. This can lead to resource leaks. Consider adding a DisposableEffect or window close handler to properly clean up KCEF resources:
DisposableEffect(Unit) {
onDispose {
kotlinx.coroutines.runBlocking {
KcefManager.dispose()
}
}
}Or handle it in the window's onCloseRequest in Main.kt before exitApplication.
| import kotlin.math.cos | ||
| import kotlin.math.min | ||
| import kotlin.math.sin |
There was a problem hiding this comment.
Unused imports: kotlin.math.cos and kotlin.math.sin are imported but never used in this file. Remove them to keep the code clean.
// Remove these unused imports:
// import kotlin.math.cos
// import kotlin.math.sin| import kotlin.math.cos | |
| import kotlin.math.min | |
| import kotlin.math.sin | |
| import kotlin.math.min |
| println("✅ KCEF already initialized") | ||
| return | ||
| } | ||
|
|
||
| if (_initState.value is KcefInitState.Initializing) { | ||
| println("⏳ KCEF initialization already in progress") | ||
| return | ||
| } | ||
|
|
||
| _initState.value = KcefInitState.Initializing | ||
|
|
||
| withContext(Dispatchers.IO) { | ||
| try { | ||
| val installDir = File(ConfigManager.getKcefInstallDir()) | ||
|
|
||
| // Create install directory if it doesn't exist | ||
| if (!installDir.exists()) { | ||
| installDir.mkdirs() | ||
| println("✅ Created KCEF install directory") | ||
| } | ||
|
|
||
| // Initialize KCEF with actual API | ||
| KCEF.init(builder = { | ||
| // Install to ~/.autodev/kcef-bundle | ||
| installDir(installDir) | ||
|
|
||
| // Progress tracking | ||
| progress { | ||
| onDownloading { | ||
| val progress = max(it, 0f) | ||
| _downloadProgress.value = progress | ||
| } | ||
| onInitialized { | ||
| _downloadProgress.value = 100f | ||
| _initState.value = KcefInitState.Initialized | ||
| } | ||
| } | ||
|
|
||
| // Settings | ||
| settings { | ||
| cachePath = File(installDir, "cache").absolutePath | ||
| } | ||
| }, onError = { error -> | ||
| val exception = error ?: Exception("Unknown KCEF error") | ||
| _initState.value = KcefInitState.Error(exception) | ||
| onError?.invoke(exception) | ||
| }, onRestartRequired = { | ||
| _initState.value = KcefInitState.RestartRequired | ||
| onRestartRequired?.invoke() | ||
| }) | ||
|
|
||
| } catch (e: Exception) { | ||
| _initState.value = KcefInitState.Error(e) | ||
| e.printStackTrace() | ||
| onError?.invoke(e) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Dispose KCEF resources | ||
| * Should be called when app is closing | ||
| */ | ||
| suspend fun dispose() { | ||
| withContext(Dispatchers.IO) { | ||
| try { | ||
| KCEF.disposeBlocking() | ||
| _initState.value = KcefInitState.Idle | ||
| _downloadProgress.value = 0f | ||
| } catch (e: Exception) { | ||
| println("⚠️ Error disposing KCEF: ${e.message}") | ||
| } | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Check if KCEF is already installed | ||
| */ | ||
| fun isInstalled(): Boolean { | ||
| val installDir = File(ConfigManager.getKcefInstallDir()) | ||
| val exists = installDir.exists() | ||
| val files = installDir.listFiles() | ||
| val fileCount = files?.size ?: 0 | ||
|
|
||
| println("📁 KCEF install directory: ${installDir.absolutePath}") | ||
| println(" - Directory exists: $exists") | ||
| println(" - File count: $fileCount") | ||
|
|
||
| if (files != null && files.isNotEmpty()) { | ||
| println(" - Files: ${files.take(5).map { it.name }.joinToString(", ")}${if (fileCount > 5) "..." else ""}") | ||
| } | ||
|
|
||
| // Check if KCEF binary exists (look for typical KCEF files) | ||
| val hasContent = exists && fileCount > 0 | ||
|
|
||
| if (hasContent) { | ||
| println("✅ KCEF appears to be installed") | ||
| } else { | ||
| println("❌ KCEF not installed, will trigger download") | ||
| } | ||
|
|
There was a problem hiding this comment.
Using println for logging instead of the IntelliJ Platform Logger. This is inconsistent with the codebase convention where Logger.getInstance(ClassName::class.java) is used for logging in IDEA plugins. Consider using a proper logger for better log management and integration with IntelliJ's logging infrastructure.
Example:
private val logger = Logger.getInstance(KcefManager::class.java)
logger.info("KCEF already initialized")| val total = data.items.sumOf { it.value } | ||
| val colors = data.items.mapIndexed { index, item -> | ||
| parseColor(item.color) ?: getDefaultColor(index) | ||
| } | ||
|
|
||
| Canvas(modifier = Modifier.fillMaxSize()) { | ||
| val diameter = min(size.width, size.height) * 0.8f | ||
| val radius = diameter / 2 | ||
| val center = Offset(size.width / 2, size.height / 2) | ||
| var startAngle = -90f | ||
|
|
||
| data.items.forEachIndexed { index, item -> | ||
| val sweepAngle = (item.value / total * 360).toFloat() |
There was a problem hiding this comment.
Potential division by zero error when all pie chart items have zero values. If total equals 0, the calculation (item.value / total * 360) will throw an ArithmeticException. Consider adding a check:
val total = data.items.sumOf { it.value }
if (total == 0.0) {
// Handle empty chart case - maybe show a message or skip rendering
return
}| println("🔍 Checking KCEF installation status...") | ||
| val installed = KcefManager.isInstalled() | ||
| println("📊 KCEF installed: $installed") | ||
|
|
||
| if (!installed) { | ||
| println("📦 KCEF not installed, starting download and initialization...") | ||
| println("⏰ This may take a few minutes on first run (80-150MB download)") | ||
| KcefManager.initialize( | ||
| onError = { error -> | ||
| println("❌ KCEF initialization failed: ${error.message}") | ||
| error.printStackTrace() | ||
| onNotification("WebView 初始化失败", error.message ?: "未知错误") | ||
| }, | ||
| onRestartRequired = { | ||
| println("🔄 KCEF requires restart") | ||
| onNotification("需要重启", "WebView 组件需要重启应用才能生效") | ||
| } | ||
| ) | ||
| println("✅ KCEF initialization request completed") | ||
| } else { | ||
| println("✅ KCEF already installed, skipping download") | ||
| } | ||
| } catch (e: Exception) { | ||
| println("⚠️ KCEF initialization error: ${e.message}") | ||
| e.printStackTrace() | ||
| } |
There was a problem hiding this comment.
Using println for logging instead of proper logging mechanism. This is a desktop application component but should still use proper logging for consistency and better log management. Consider using AutoDevLogger which is already initialized in Main.kt.
Example:
AutoDevLogger.info("KcefManager") { "🔍 Checking KCEF installation status..." }| scope.launch { | ||
| try { | ||
| println("🔍 Checking KCEF installation status...") | ||
| val installed = KcefManager.isInstalled() | ||
| println("📊 KCEF installed: $installed") | ||
|
|
||
| if (!installed) { | ||
| println("📦 KCEF not installed, starting download and initialization...") | ||
| println("⏰ This may take a few minutes on first run (80-150MB download)") | ||
| KcefManager.initialize( | ||
| onError = { error -> | ||
| println("❌ KCEF initialization failed: ${error.message}") | ||
| error.printStackTrace() | ||
| onNotification("WebView 初始化失败", error.message ?: "未知错误") | ||
| }, | ||
| onRestartRequired = { | ||
| println("🔄 KCEF requires restart") | ||
| onNotification("需要重启", "WebView 组件需要重启应用才能生效") | ||
| } | ||
| ) | ||
| println("✅ KCEF initialization request completed") | ||
| } else { | ||
| println("✅ KCEF already installed, skipping download") | ||
| } | ||
| } catch (e: Exception) { | ||
| println("⚠️ KCEF initialization error: ${e.message}") | ||
| e.printStackTrace() | ||
| } |
There was a problem hiding this comment.
Redundant scope.launch inside LaunchedEffect. The LaunchedEffect block already runs in a coroutine context, so wrapping the code in scope.launch is unnecessary and creates an additional coroutine layer. Remove the scope.launch wrapper:
LaunchedEffect(Unit) {
try {
println("🔍 Checking KCEF installation status...")
val installed = KcefManager.isInstalled()
// ... rest of the code
} catch (e: Exception) {
// ...
}
}| scope.launch { | |
| try { | |
| println("🔍 Checking KCEF installation status...") | |
| val installed = KcefManager.isInstalled() | |
| println("📊 KCEF installed: $installed") | |
| if (!installed) { | |
| println("📦 KCEF not installed, starting download and initialization...") | |
| println("⏰ This may take a few minutes on first run (80-150MB download)") | |
| KcefManager.initialize( | |
| onError = { error -> | |
| println("❌ KCEF initialization failed: ${error.message}") | |
| error.printStackTrace() | |
| onNotification("WebView 初始化失败", error.message ?: "未知错误") | |
| }, | |
| onRestartRequired = { | |
| println("🔄 KCEF requires restart") | |
| onNotification("需要重启", "WebView 组件需要重启应用才能生效") | |
| } | |
| ) | |
| println("✅ KCEF initialization request completed") | |
| } else { | |
| println("✅ KCEF already installed, skipping download") | |
| } | |
| } catch (e: Exception) { | |
| println("⚠️ KCEF initialization error: ${e.message}") | |
| e.printStackTrace() | |
| } | |
| try { | |
| println("🔍 Checking KCEF installation status...") | |
| val installed = KcefManager.isInstalled() | |
| println("📊 KCEF installed: $installed") | |
| if (!installed) { | |
| println("📦 KCEF not installed, starting download and initialization...") | |
| println("⏰ This may take a few minutes on first run (80-150MB download)") | |
| KcefManager.initialize( | |
| onError = { error -> | |
| println("❌ KCEF initialization failed: ${error.message}") | |
| error.printStackTrace() | |
| onNotification("WebView 初始化失败", error.message ?: "未知错误") | |
| }, | |
| onRestartRequired = { | |
| println("🔄 KCEF requires restart") | |
| onNotification("需要重启", "WebView 组件需要重启应用才能生效") | |
| } | |
| ) | |
| println("✅ KCEF initialization request completed") | |
| } else { | |
| println("✅ KCEF already installed, skipping download") | |
| } | |
| } catch (e: Exception) { | |
| println("⚠️ KCEF initialization error: ${e.message}") | |
| e.printStackTrace() |
| // nodejs { | ||
| // Configure Node.js target for CLI | ||
| } | ||
| // } |
There was a problem hiding this comment.
Commented-out code should be removed instead of left in the codebase. If this code was intentionally disabled, add a comment explaining why. Otherwise, remove it to keep the codebase clean.
Either remove the commented code or add an explanation:
// Note: nodejs target disabled for desktop-only builds to reduce bundle size
// nodejs {
// // Configure Node.js target for CLI
// }| "mermaid", "mmd" -> { | ||
| val disposable = parentDisposable ?: Disposer.newDisposable("AgentSketchBlock") | ||
| IdeaMermaidRenderer( | ||
| mermaidCode = item.code, | ||
| project = project, | ||
| isDarkTheme = true, | ||
| parentDisposable = disposable, | ||
| modifier = Modifier.fillMaxWidth() | ||
| ) | ||
| } |
There was a problem hiding this comment.
Potential resource leak: A new Disposable is created when parentDisposable is null, but it's never disposed. This can lead to memory leaks. The disposable should be properly managed.
Consider either:
- Requiring
parentDisposableto not be null (since this is in the IDEA plugin context where disposables are always available) - Using
DisposableEffectto properly manage the lifecycle:
"mermaid", "mmd" -> {
DisposableEffect(Unit) {
val disposable = parentDisposable ?: Disposer.newDisposable("AgentSketchBlock")
onDispose {
if (parentDisposable == null) {
Disposer.dispose(disposable)
}
}
}
IdeaMermaidRenderer(
mermaidCode = item.code,
project = project,
isDarkTheme = true,
parentDisposable = disposable,
modifier = Modifier.fillMaxWidth()
)
}There was a problem hiding this comment.
Actionable comments posted: 0
♻️ Duplicate comments (1)
mpp-ui/src/jvmMain/kotlin/cc/unitmesh/devins/ui/kcef/KcefManager.kt (1)
32-94: Guardinitialize()against concurrent callers to avoid double initializationAs previously noted, the
initialize()guards currently use a non‑atomic check‑then‑set on_initState. Two concurrent callers can both see a non‑Initializing/non‑Initialized state and then both set_initStatetoInitializing, causing duplicate KCEF initialization attempts.You can fix this with a short critical section around the state checks and transition, and also reset
downloadProgresswhen starting a new init:object KcefManager { + private val initializationLock = Any() @@ - suspend fun initialize( + suspend fun initialize( onError: ((Throwable) -> Unit)? = null, onRestartRequired: (() -> Unit)? = null ) { - if (_initState.value is KcefInitState.Initialized) { - println("✅ KCEF already initialized") - return - } - - if (_initState.value is KcefInitState.Initializing) { - println("⏳ KCEF initialization already in progress") - return - } - - _initState.value = KcefInitState.Initializing + synchronized(initializationLock) { + when (_initState.value) { + is KcefInitState.Initialized -> { + println("✅ KCEF already initialized") + return + } + is KcefInitState.Initializing -> { + println("⏳ KCEF initialization already in progress") + return + } + else -> { + _initState.value = KcefInitState.Initializing + _downloadProgress.value = 0f + } + } + } withContext(Dispatchers.IO) { try { val installDir = File(ConfigManager.getKcefInstallDir()) // ...This ensures only one caller flips the state from non‑initialized to
Initializing, and callers racing in after that will bail out early.
🧹 Nitpick comments (2)
mpp-ui/src/jvmMain/kotlin/cc/unitmesh/devins/ui/kcef/MermaidWebViewPreview.kt (1)
21-56: Consider delegating KCEF initialization here toKcefManagerand aligning cache path behaviorThis window duplicates KCEF.init logic (including progress, installDir, and error handling) instead of reusing
KcefManager.initialize(). That increases the risk of subtle divergence from the main app path (e.g., different cache location, different callbacks, or future config flags).You might simplify this preview and keep behavior consistent by:
- Calling
KcefManager.initialize()from theLaunchedEffectinstead ofKCEF.initdirectly.- Observing
KcefManager.initState/KcefManager.downloadProgressfor UI instead of local state mirrors.- If you keep direct
KCEF.inithere, at least mirrorKcefManager’s behavior:
- Ensure
installDirexists (mkdirs()) before passing it.- Use a cache path under
installDir(e.g.,File(installDir, "cache").absolutePath) to keep all KCEF artifacts in the same bundle dir.Example adjustment:
- val installDir = File(ConfigManager.getKcefInstallDir()) - KCEF.init(builder = { - installDir(installDir) + val installDir = File(ConfigManager.getKcefInstallDir()).apply { mkdirs() } + KCEF.init(builder = { + installDir(installDir) ... - settings { - cachePath = File("kcef-cache").absolutePath - } + settings { + cachePath = File(installDir, "cache").absolutePath + }mpp-ui/src/jvmMain/kotlin/cc/unitmesh/devins/ui/kcef/KcefManager.kt (1)
100-139: Dispose and install‑check behavior are acceptable but could be tightenedFunctionally this works, but there are a couple of small robustness improvements you might consider:
dispose()(Lines 100–109): you always callKCEF.disposeBlocking()regardless of the currentinitState. Ifdispose()is called before initialization completes or after anError, this relies on KCEF being tolerant of extra dispose calls. If the library doesn’t guarantee that, you may want to guard withif (_initState.value is KcefInitState.Initialized || _initState.value is KcefInitState.Initializing)before disposing to avoid undefined behavior.isInstalled()(Lines 115–139): right nowhasContentisexists && fileCount > 0, which treats any non‑empty directory as “installed”. If partial or corrupt downloads are possible, you might want a slightly stricter heuristic (e.g., checking for a few known‑critical KCEF files) before returningtrue. Logging the path and a sample of filenames is helpful for debugging, so I’d keep that.These are not blockers, but tightening them would make lifecycle and install detection more predictable.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
mpp-ui/src/jvmMain/kotlin/cc/unitmesh/devins/ui/kcef/KcefManager.kt(1 hunks)mpp-ui/src/jvmMain/kotlin/cc/unitmesh/devins/ui/kcef/MermaidWebViewPreview.kt(8 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.kt
📄 CodeRabbit inference engine (AGENTS.md)
**/*.kt: Useexpect/actualpattern for platform-specific code (for example:Platformimplementations)
Use concrete classes (not interfaces) andPromise(notFlow) for @JsExport annotations
Avoid emoji and UTF-8 characters in WASM code
When modifyingCodingAgentRenderer, update ALL implementations:DefaultCodingAgentRenderer,ComposeRenderer,JewelRenderer,ServerSideRenderer, andJsRendererAdapterin Kotlin
When modifyingCodingAgentRenderer, update JVM CLI implementations:CodingCliRendererandConsoleRenderer
UseAutoDevColorsorMaterialTheme.colorSchemefor Compose styling
Files:
mpp-ui/src/jvmMain/kotlin/cc/unitmesh/devins/ui/kcef/KcefManager.ktmpp-ui/src/jvmMain/kotlin/cc/unitmesh/devins/ui/kcef/MermaidWebViewPreview.kt
**/*.{kt,ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
Never use hardcoded colors - always use design tokens from the design system
Files:
mpp-ui/src/jvmMain/kotlin/cc/unitmesh/devins/ui/kcef/KcefManager.ktmpp-ui/src/jvmMain/kotlin/cc/unitmesh/devins/ui/kcef/MermaidWebViewPreview.kt
🧠 Learnings (2)
📚 Learning: 2025-12-07T08:40:10.850Z
Learnt from: CR
Repo: phodal/auto-dev PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-07T08:40:10.850Z
Learning: Applies to **/*.kt : When modifying `CodingAgentRenderer`, update ALL implementations: `DefaultCodingAgentRenderer`, `ComposeRenderer`, `JewelRenderer`, `ServerSideRenderer`, and `JsRendererAdapter` in Kotlin
Applied to files:
mpp-ui/src/jvmMain/kotlin/cc/unitmesh/devins/ui/kcef/MermaidWebViewPreview.kt
📚 Learning: 2025-12-07T08:40:10.850Z
Learnt from: CR
Repo: phodal/auto-dev PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-07T08:40:10.850Z
Learning: Applies to **/*.kt : Use `AutoDevColors` or `MaterialTheme.colorScheme` for Compose styling
Applied to files:
mpp-ui/src/jvmMain/kotlin/cc/unitmesh/devins/ui/kcef/MermaidWebViewPreview.kt
🔇 Additional comments (2)
mpp-ui/src/jvmMain/kotlin/cc/unitmesh/devins/ui/kcef/MermaidWebViewPreview.kt (1)
113-148: Compose UI and theming usage inMainMermaidContentlook good
MainMermaidContentusesMaterialThemeandMaterialTheme.colorScheme.backgroundwithout introducing any hardcoded colors, and the layout logic is straightforward and side‑effect free. This aligns with the design‑token/theming guidelines for Compose. No changes needed here.mpp-ui/src/jvmMain/kotlin/cc/unitmesh/devins/ui/kcef/KcefManager.kt (1)
19-25: State exposure viaStateFlowis clean and appropriateUsing private
MutableStateFlowwith publicStateFlowviaasStateFlow()for bothinitStateanddownloadProgressis a solid pattern that keeps mutation localized insideKcefManagerwhile giving UI a reactive, read‑only view. No changes needed.
🎯 Changes
This PR integrates KCEF (Kotlin Chromium Embedded Framework) into the Desktop version with a compact, non-intrusive progress bar.
✨ Features
KCEF Installation
~/.autodev/kcef-bundleProgress Bar UI
Platform Support
📝 Implementation Details
Files Changed
getKcefInstallDir()method for all platformsArchitecture
🎨 UI Preview
Progress Bar (Compact):
🧪 Testing
rm -rf ~/.autodev/kcef-bundle./gradlew :mpp-ui:runls -lh ~/.autodev/kcef-bundle📚 Dependencies
io.github.kevinnzou:compose-webview-multiplatform:2.0.3dev.datlag.kcef.KCEF🔗 References
Summary by CodeRabbit
✏️ Tip: You can customize this high-level summary in your review settings.