Skip to content

feat(desktop): Integrate KCEF WebView with Background Download#505

Merged
phodal merged 4 commits intomasterfrom
feat/kcef-webview-integration
Dec 8, 2025
Merged

feat(desktop): Integrate KCEF WebView with Background Download#505
phodal merged 4 commits intomasterfrom
feat/kcef-webview-integration

Conversation

@phodal
Copy link
Owner

@phodal phodal commented Dec 8, 2025

🎯 Changes

This PR integrates KCEF (Kotlin Chromium Embedded Framework) into the Desktop version with a compact, non-intrusive progress bar.

✨ Features

KCEF Installation

  • Install Location: ~/.autodev/kcef-bundle
  • User Control: Users can manually download and place KCEF in this directory
  • Auto Download: Automatic download from GitHub on first run (~80-150MB)

Progress Bar UI

  • Compact Design: Single-line layout at bottom of window
  • Real-time Progress: Live download progress (0-100%)
  • Non-blocking: Download runs in background, app remains usable
  • User Dismissible: Can be closed without stopping download
  • Auto-hide: Disappears automatically on completion

Platform Support

  • ✅ macOS (Intel & Apple Silicon)
  • ✅ Windows (x64)
  • ✅ Linux (x64)
  • ❌ Other platforms (KCEF not available)

📝 Implementation Details

Files Changed

  • ConfigManager: Added getKcefInstallDir() method for all platforms
  • KcefManager: Handles KCEF initialization and lifecycle management
  • KcefProgressBar: Compact progress UI component
  • DesktopAutoDevApp: Desktop-specific wrapper for KCEF integration
  • ProGuard Rules: Added KCEF keep rules

Architecture

Main.kt
  └─> DesktopAutoDevApp (Desktop wrapper)
      ├─> LaunchedEffect: KcefManager.initialize()
      │   └─> KCEF.init() API
      │   └─> Real-time progress tracking
      │
      └─> KcefProgressBar (Bottom overlay)
          └─> Shows download status

🎨 UI Preview

Progress Bar (Compact):

┌─────────────────────────────────────────────┐
│ 🔽 正在下载 WebView (45%) ▓▓▓░░░ 后台进行 [x]│
└─────────────────────────────────────────────┘

🧪 Testing

  1. Delete existing KCEF: rm -rf ~/.autodev/kcef-bundle
  2. Run Desktop app: ./gradlew :mpp-ui:run
  3. Observe progress bar and console logs
  4. Verify download: ls -lh ~/.autodev/kcef-bundle

📚 Dependencies

  • io.github.kevinnzou:compose-webview-multiplatform:2.0.3
  • Uses KCEF API: dev.datlag.kcef.KCEF

⚠️ Notes

  • First run requires internet connection
  • Download size: 80-150MB depending on platform
  • KCEF files stored in user home directory
  • ProGuard rules included for release builds

🔗 References

Summary by CodeRabbit

  • New Features
    • Chart visualization (pie, line, column, row) and parser for agent sketches; raw-code fallback when parsing fails
    • Mermaid diagrams and improved renderer selection in timeline/agent sketch views
    • Desktop wrapper app with KCEF lifecycle: init progress indicator and non-blocking startup UI
    • Browser-based configuration storage for web/wasm environments and cross-platform config access

✏️ Tip: You can customize this high-level summary in your review settings.

- 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.
Copilot AI review requested due to automatic review settings December 8, 2025 15:58
@coderabbitai
Copy link

coderabbitai bot commented Dec 8, 2025

Walkthrough

Adds 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

Cohort / File(s) Summary
ConfigManager Platform APIs
mpp-core/src/commonMain/kotlin/cc/unitmesh/config/ConfigManager.kt, mpp-core/src/jvmMain/kotlin/cc/unitmesh/config/ConfigManager.jvm.kt, mpp-core/src/androidMain/kotlin/cc/unitmesh/config/ConfigManager.android.kt, mpp-core/src/iosMain/kotlin/cc/unitmesh/config/ConfigManager.ios.kt, mpp-core/src/jsMain/kotlin/cc/unitmesh/config/ConfigManager.js.kt
Added getKcefInstallDir(): String to the expect/actual ConfigManager: JVM returns configDir/kcef-bundle absolute path; Android/iOS/JS return empty string with platform-unavailable comments.
WASM/JS Browser Storage & ConfigManager
mpp-core/src/wasmJsMain/kotlin/cc/unitmesh/config/BrowserStorage.kt, mpp-core/src/wasmJsMain/kotlin/cc/unitmesh/config/ConfigManager.wasmJs.kt
Added BrowserStorage wrapper over localStorage (set/get/remove/clear/keys/hasItem) and a full WASM/JS ConfigManager implementation using localStorage, with YAML/JSON parsing, config lifecycle (load/save/delete/set active), tool config handling, MCP/remote server persistence, and helpers.
Chart Data Model & Parsing
mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/renderer/sketch/chart/IdeaChartData.kt, mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/renderer/sketch/chart/IdeaChartParser.kt
Added chart model types (ChartType, ChartConfig, ChartDataContent variants, PieItem, LineItem, BarGroup, BarValue, PieStyle) and IdeaChartParser that parses YAML or simple-text chart definitions into ChartConfig.
Chart Rendering & Sketch Routing
mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/renderer/sketch/chart/IdeaChartRenderer.kt, mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/renderer/sketch/IdeaSketchRenderer.kt
Added IdeaChartRenderer composable (pie/line/column/row rendering with Canvas, color resolution, legend, fallback) and updated sketch renderer to route chart/graph blocks to IdeaChartRenderer.
Timeline & Sketch Components
mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/components/timeline/IdeaTimelineContent.kt
Extended IdeaAgentSketchBlockBubble signature to accept optional Project and Disposable, selected renderers by language (chart/mermaid) and added RenderCodeFallback for other languages.
KCEF Desktop Integration
mpp-ui/src/jvmMain/kotlin/cc/unitmesh/devins/ui/kcef/KcefManager.kt, mpp-ui/src/jvmMain/kotlin/cc/unitmesh/devins/ui/kcef/KcefProgressBar.kt, mpp-ui/src/jvmMain/kotlin/cc/unitmesh/devins/ui/compose/DesktopAutoDevApp.kt
Added KcefManager singleton with StateFlow for init state and progress, initialize/dispose/isInstalled API and state machine; KcefProgressBar composable for bottom progress/error UI; DesktopAutoDevApp wrapper that triggers background KCEF init and overlays progress UI.
Mermaid WebView / KCEF usage
mpp-ui/src/jvmMain/kotlin/cc/unitmesh/devins/ui/kcef/MermaidWebViewPreview.kt
Set KCEF installDir using ConfigManager.getKcefInstallDir() during KCEF.init and adjusted package/imports.
UI Entry Point
mpp-ui/src/jvmMain/kotlin/cc/unitmesh/devins/ui/Main.kt
Replaced AutoDevApp(...) usage with DesktopAutoDevApp(...) and updated import.
Chart data types
mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/renderer/sketch/chart/IdeaChartData.kt
Introduced chart data model files (see Chart Data Model & Parsing cohort).
Build & Gradle changes
mpp-idea/build.gradle.kts, mpp-ui/build.gradle.kts
Switched from compileOnly to runtime Ktor client dependencies with extensive transitive exclusions for kotlinx and IO modules to avoid IntelliJ conflicts; added Kotlin Gradle plugin opt-in and commented-out Node.js target in JS IR.
ProGuard rules
mpp-ui/proguard-rules.pro
Added keep/suppress rules for KCEF (org.cef.**) and Compose WebView Multiplatform (com.multiplatform.webview.**).

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
Loading
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
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • Pay extra attention to:
    • KcefManager state transitions, concurrency guards, and disposal (KCEF callbacks and restart handling).
    • WASM ConfigManager persistence and YAML/JSON parsing edge cases.
    • Canvas drawing math and color parsing in IdeaChartRenderer (visual correctness and performance).
    • Gradle dependency exclusion correctness to avoid runtime classpath issues in IntelliJ.

Possibly related PRs

"I'm a rabbit with a tiny pen,
I stored configs in local den,
Charts leap high and KCEF lands,
Progress bars hum by gentle hands,
Hooray — a hop for every friend!" 🥕🐇

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 19.77% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat(desktop): Integrate KCEF WebView with Background Download' clearly and concisely summarizes the main change—integrating KCEF into the desktop app with background download capability—which is the primary objective across all file modifications.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/kcef-webview-integration

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 -keep rule for SwingDispatcherFactory is 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 adding blockIsComplete guard for streaming consistency.

Other renderers like mermaid (line 127) check blockIsComplete before 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 Exception silently 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.Gray directly. 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: curvedEdges is not implemented in the renderer.

The curvedEdges property defaults to true but RenderLineChart in IdeaChartRenderer.kt always 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: width property is currently unused.

ChartConfig.width is defined but the renderer only uses height. 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 console object name could shadow references to the standard JavaScript console if this package is imported elsewhere. Consider using WasmConsole or Console (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 using AutoDevLogger instead of println.

The codebase uses AutoDevLogger for structured logging (as seen in Main.kt). Replace println calls with AutoDevLogger.info or 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 DesktopAutoDevApp within a coroutine. Consider making it a suspend function with withContext(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 from mkdirs().

mkdirs() returns false if 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

📥 Commits

Reviewing files that changed from the base of the PR and between fab6097 and 68b84cb.

📒 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: Use expect/actual pattern for platform-specific code (for example: Platform implementations)
Use concrete classes (not interfaces) and Promise (not Flow) for @JsExport annotations
Avoid emoji and UTF-8 characters in WASM code
When modifying CodingAgentRenderer, update ALL implementations: DefaultCodingAgentRenderer, ComposeRenderer, JewelRenderer, ServerSideRenderer, and JsRendererAdapter in Kotlin
When modifying CodingAgentRenderer, update JVM CLI implementations: CodingCliRenderer and ConsoleRenderer
Use AutoDevColors or MaterialTheme.colorScheme for Compose styling

Files:

  • mpp-core/src/jsMain/kotlin/cc/unitmesh/config/ConfigManager.js.kt
  • mpp-core/src/androidMain/kotlin/cc/unitmesh/config/ConfigManager.android.kt
  • mpp-core/src/jvmMain/kotlin/cc/unitmesh/config/ConfigManager.jvm.kt
  • mpp-core/src/wasmJsMain/kotlin/cc/unitmesh/config/BrowserStorage.kt
  • mpp-core/src/iosMain/kotlin/cc/unitmesh/config/ConfigManager.ios.kt
  • mpp-core/src/commonMain/kotlin/cc/unitmesh/config/ConfigManager.kt
  • mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/renderer/sketch/IdeaSketchRenderer.kt
  • mpp-ui/src/jvmMain/kotlin/cc/unitmesh/devins/ui/kcef/KcefManager.kt
  • mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/renderer/sketch/chart/IdeaChartParser.kt
  • mpp-ui/src/jvmMain/kotlin/cc/unitmesh/devins/ui/kcef/KcefProgressBar.kt
  • mpp-ui/src/jvmMain/kotlin/cc/unitmesh/devins/ui/compose/DesktopAutoDevApp.kt
  • mpp-ui/src/jvmMain/kotlin/cc/unitmesh/devins/ui/Main.kt
  • mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/renderer/sketch/chart/IdeaChartRenderer.kt
  • mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/renderer/sketch/chart/IdeaChartData.kt
  • mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/components/timeline/IdeaTimelineContent.kt
  • mpp-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.kt
  • mpp-core/src/androidMain/kotlin/cc/unitmesh/config/ConfigManager.android.kt
  • mpp-core/src/jvmMain/kotlin/cc/unitmesh/config/ConfigManager.jvm.kt
  • mpp-core/src/wasmJsMain/kotlin/cc/unitmesh/config/BrowserStorage.kt
  • mpp-core/src/iosMain/kotlin/cc/unitmesh/config/ConfigManager.ios.kt
  • mpp-core/src/commonMain/kotlin/cc/unitmesh/config/ConfigManager.kt
  • mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/renderer/sketch/IdeaSketchRenderer.kt
  • mpp-ui/src/jvmMain/kotlin/cc/unitmesh/devins/ui/kcef/KcefManager.kt
  • mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/renderer/sketch/chart/IdeaChartParser.kt
  • mpp-ui/src/jvmMain/kotlin/cc/unitmesh/devins/ui/kcef/KcefProgressBar.kt
  • mpp-ui/src/jvmMain/kotlin/cc/unitmesh/devins/ui/compose/DesktopAutoDevApp.kt
  • mpp-ui/src/jvmMain/kotlin/cc/unitmesh/devins/ui/Main.kt
  • mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/renderer/sketch/chart/IdeaChartRenderer.kt
  • mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/renderer/sketch/chart/IdeaChartData.kt
  • mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/components/timeline/IdeaTimelineContent.kt
  • mpp-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.pro
  • mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/renderer/sketch/IdeaSketchRenderer.kt
  • mpp-idea/build.gradle.kts
  • mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/renderer/sketch/chart/IdeaChartRenderer.kt
  • mpp-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.pro
  • mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/renderer/sketch/IdeaSketchRenderer.kt
  • mpp-idea/build.gradle.kts
  • mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/renderer/sketch/chart/IdeaChartRenderer.kt
  • mpp-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 of ExperimentalKotlinGradlePluginApi opt-in.

The file-level @file:OptIn annotation 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 the nodejs {} 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-io and kotlinx-io-core (and jvm variants) as part of a coherent dependency management approach. Since ktor-serialization-kotlinx-json is 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-json is excluded (mpp-idea provides it explicitly), and why ktor-client-content-negotiation is 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 JewelTheme for 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 BrowserStorage object correctly wraps all localStorage operations with try/catch blocks and provides sensible fallbacks (null for reads, empty list for keys). The external Storage interface 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.error for structured logging is preferable to println used 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 uses YamlUtils in the WASM target, and full WASM support is confirmed by the kaml library dependency and comprehensive WASM-specific tests that validate loadAs() and dump() 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 of MaterialTheme.colorScheme for styling.

The use of MaterialTheme.colorScheme.surfaceVariant with 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 KcefInitState sealed class clearly models all possible states with appropriate data (Error includes the exception). Using data object for stateless variants is idiomatic Kotlin.

Comment on lines 319 to 354
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")
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ 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.

Comment on lines +162 to +170
"mermaid", "mmd" -> {
val disposable = parentDisposable ?: Disposer.newDisposable("AgentSketchBlock")
IdeaMermaidRenderer(
mermaidCode = item.code,
project = project,
isDarkTheme = true,
parentDisposable = disposable,
modifier = Modifier.fillMaxWidth()
)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

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.

Comment on lines +42 to +72
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()
}
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

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.

Comment on lines +36 to +46
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
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

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.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +20 to +102
@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")
}
)
}
}
Copy link

Copilot AI Dec 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Comment on lines +23 to +25
import kotlin.math.cos
import kotlin.math.min
import kotlin.math.sin
Copy link

Copilot AI Dec 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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
Suggested change
import kotlin.math.cos
import kotlin.math.min
import kotlin.math.sin
import kotlin.math.min

Copilot uses AI. Check for mistakes.
Comment on lines +37 to +137
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")
}

Copy link

Copilot AI Dec 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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")

Copilot uses AI. Check for mistakes.
Comment on lines +95 to +107
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()
Copy link

Copilot AI Dec 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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
}

Copilot uses AI. Check for mistakes.
Comment on lines +45 to +70
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()
}
Copy link

Copilot AI Dec 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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..." }

Copilot uses AI. Check for mistakes.
Comment on lines +43 to +70
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()
}
Copy link

Copilot AI Dec 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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) {
        // ...
    }
}
Suggested change
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()

Copilot uses AI. Check for mistakes.
Comment on lines 95 to 97
// nodejs {
// Configure Node.js target for CLI
}
// }
Copy link

Copilot AI Dec 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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
// }

Copilot uses AI. Check for mistakes.
Comment on lines +162 to +171
"mermaid", "mmd" -> {
val disposable = parentDisposable ?: Disposer.newDisposable("AgentSketchBlock")
IdeaMermaidRenderer(
mermaidCode = item.code,
project = project,
isDarkTheme = true,
parentDisposable = disposable,
modifier = Modifier.fillMaxWidth()
)
}
Copy link

Copilot AI Dec 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:

  1. Requiring parentDisposable to not be null (since this is in the IDEA plugin context where disposables are always available)
  2. Using DisposableEffect to 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()
    )
}

Copilot uses AI. Check for mistakes.
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (1)
mpp-ui/src/jvmMain/kotlin/cc/unitmesh/devins/ui/kcef/KcefManager.kt (1)

32-94: Guard initialize() against concurrent callers to avoid double initialization

As 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 _initState to Initializing, causing duplicate KCEF initialization attempts.

You can fix this with a short critical section around the state checks and transition, and also reset downloadProgress when 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 to KcefManager and aligning cache path behavior

This 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 the LaunchedEffect instead of KCEF.init directly.
  • Observing KcefManager.initState / KcefManager.downloadProgress for UI instead of local state mirrors.
  • If you keep direct KCEF.init here, at least mirror KcefManager’s behavior:
    • Ensure installDir exists (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 tightened

Functionally this works, but there are a couple of small robustness improvements you might consider:

  • dispose() (Lines 100–109): you always call KCEF.disposeBlocking() regardless of the current initState. If dispose() is called before initialization completes or after an Error, this relies on KCEF being tolerant of extra dispose calls. If the library doesn’t guarantee that, you may want to guard with if (_initState.value is KcefInitState.Initialized || _initState.value is KcefInitState.Initializing) before disposing to avoid undefined behavior.
  • isInstalled() (Lines 115–139): right now hasContent is exists && 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 returning true. 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

📥 Commits

Reviewing files that changed from the base of the PR and between 68b84cb and a07e2e8.

📒 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: Use expect/actual pattern for platform-specific code (for example: Platform implementations)
Use concrete classes (not interfaces) and Promise (not Flow) for @JsExport annotations
Avoid emoji and UTF-8 characters in WASM code
When modifying CodingAgentRenderer, update ALL implementations: DefaultCodingAgentRenderer, ComposeRenderer, JewelRenderer, ServerSideRenderer, and JsRendererAdapter in Kotlin
When modifying CodingAgentRenderer, update JVM CLI implementations: CodingCliRenderer and ConsoleRenderer
Use AutoDevColors or MaterialTheme.colorScheme for Compose styling

Files:

  • mpp-ui/src/jvmMain/kotlin/cc/unitmesh/devins/ui/kcef/KcefManager.kt
  • mpp-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.kt
  • mpp-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 in MainMermaidContent look good

MainMermaidContent uses MaterialTheme and MaterialTheme.colorScheme.background without 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 via StateFlow is clean and appropriate

Using private MutableStateFlow with public StateFlow via asStateFlow() for both initState and downloadProgress is a solid pattern that keeps mutation localized inside KcefManager while giving UI a reactive, read‑only view. No changes needed.

@phodal phodal merged commit 0e30a3c into master Dec 8, 2025
2 of 3 checks passed
@phodal phodal deleted the feat/kcef-webview-integration branch December 8, 2025 23:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

Comments