Skip to content

Comments

feat(omnibar): implement Omnibar command palette for Compose and IDEA#498

Merged
phodal merged 9 commits intomasterfrom
feature/omnibar-command-palette
Dec 7, 2025
Merged

feat(omnibar): implement Omnibar command palette for Compose and IDEA#498
phodal merged 9 commits intomasterfrom
feature/omnibar-command-palette

Conversation

@phodal
Copy link
Owner

@phodal phodal commented Dec 7, 2025

Summary

Implements GitHub Issue #495 - Omnibar command palette similar to Linear and Raycast.

Features

  • Wide Floating Layout - 800dp wide command palette with semi-transparent dark backdrop
  • Keyboard Navigation - Up/Down arrows, Enter to select, Escape to close
  • Fuzzy Search - Weighted ranking (recency > context > match quality > base weight)
  • Abstracted Architecture - OmnibarDataProvider interface for platform-specific implementations

Changes

mpp-ui (Compose Multiplatform - Shared Layer)

  • OmnibarItem.kt - Data model with OmnibarItemType enum, OmnibarItem data class, OmnibarActionResult sealed class, and OmnibarDataProvider interface
  • OmnibarSearchEngine.kt - Fuzzy search engine with weighted ranking
  • Omnibar.kt - Main Compose UI component with 800dp wide floating layout
  • AutoDevComposeIcons.kt - Added Search icon
  • AutoDevColors.kt - Added Void.overlay color (50% transparent)

mpp-idea (IntelliJ IDEA Plugin)

  • IdeaOmnibarDataProvider.kt - Implementation of OmnibarDataProvider providing built-in and custom commands
  • IdeaOmnibarDialog.kt - DialogWrapper with JewelComposePanel for native IDE integration
  • IdeaOmnibarAction.kt - AnAction for opening Omnibar
  • plugin.xml - Registered action with keyboard shortcuts

Build Configuration

  • Added maven-publish plugin to xuiper-ui for mavenLocal publishing
  • Updated publishDepsForIdea task to include xuiper-ui dependency

Usage

In IntelliJ IDEA:

  • Press Cmd+Shift+P (Mac) or Ctrl+Shift+P (Windows/Linux)
  • Or: Tools menu - Open Omnibar

Closes #495

Summary by CodeRabbit

  • New Features

    • Omnibar command palette added across desktop, webview, and IDE plugins with fuzzy search, keyboard navigation, recent items, and action handling.
    • Animated Xiuper launch screen with optional reduced-motion skipping.
  • Improvements

    • Cross-platform reduced-motion detection propagated to UI.
    • Expanded color palette, animation system (easings/springs), and added Terminal icon.
  • Chores

    • Added publishing support for an additional UI artifact.
  • Docs

    • Clarified language guidelines for code comments and user-facing strings.

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

## Summary
Implement the Xiuper launch animation for Desktop app based on the
design system specification (xuiper-ds.md).

## Changes

### New Files
- AutoDevAnimation.kt: Animation curves and timing constants
  - EaseXiu: cubic-bezier(0.16, 1, 0.3, 1) - ultra-fast response
  - EaseStream: linear - for AI code streaming
  - SpringTactile: bounce effect for micro-interactions
  - Duration constants for Launch animation phases

- XiuperLaunchScreen.kt: Complete launch animation component
  - 6-phase animation state machine
  - Multi-layer glow effects (AI purple + Xiu cyan)
  - Speed lines for forward momentum visual
  - Breathing pulse animation
  - Reduced-motion accessibility support

### Modified Files
- Platform.kt (all platforms): Added prefersReducedMotion() function
  - JVM: macOS/Windows/Linux system settings detection
  - Android: Settings.Global accessibility check
  - JS: prefers-reduced-motion media query
  - iOS: UIAccessibility.isReduceMotionEnabled
  - WASM: Returns false (no system access)

- Main.kt: Integrated launch screen with splash state management
  - --skip-splash command line argument support
  - ThemeManager.ThemeMode.DARK for launch screen

## Animation Timeline (2.2s total)
- [0-100ms]    Initial delay - 'gathering power'
- [100-500ms]  Glow expansion - energy gathering
- [500-1000ms] Logo scale up with ease-xiu curve
- [1000-1300ms] Text slide in from below
- [1300-1900ms] Brand display + breathing glow
- [1900-2200ms] Fade out to main interface

## Design System Alignment
- Neon-Noir aesthetic with Void background (#0B0E14)
- Dual-color energy system (xiu cyan + ai purple)
- Speed lines for 'Forward Momentum'
- Breathing glow for 'never static' principle

Closes #493
Use kotlin.math.PI for angle conversion to improve consistency.
Refine platform-specific prefersReducedMotion implementations for better accuracy and performance. Also correct "Xuiper" to "Xiuper" in UI text.
Replace deprecated UIAccessibility.isReduceMotionEnabled with UIAccessibilityIsReduceMotionEnabled for prefersReducedMotion detection.
Simplifies reduced motion detection for WASM by always returning false due to media query limitations.
Implements GitHub Issue #495 - Omnibar command palette similar to Linear and Raycast.

## Changes

### mpp-ui (Compose Multiplatform - Shared Layer)
- Add OmnibarItem.kt: Data model with OmnibarItemType enum, OmnibarItem data class, OmnibarActionResult sealed class, and OmnibarDataProvider interface
- Add OmnibarSearchEngine.kt: Fuzzy search engine with weighted ranking (recency > context > match quality > base weight)
- Add Omnibar.kt: Main Compose UI component with 800dp wide floating layout, semi-transparent backdrop, keyboard navigation (Up/Down/Enter/Escape)
- Add Search icon to AutoDevComposeIcons
- Add Void.overlay color (50% transparent) to AutoDevColors

### mpp-idea (IntelliJ IDEA Plugin)
- Add IdeaOmnibarDataProvider.kt: Implementation of OmnibarDataProvider providing built-in and custom commands
- Add IdeaOmnibarDialog.kt: DialogWrapper with JewelComposePanel for native IDE integration
- Add IdeaOmnibarAction.kt: AnAction for opening Omnibar
- Register action with keyboard shortcuts (Ctrl+Shift+P / Cmd+Shift+P) in plugin.xml

### Build Configuration
- Add maven-publish plugin to xuiper-ui for mavenLocal publishing
- Update publishDepsForIdea task to include xuiper-ui dependency

## Usage
In IntelliJ IDEA: Press Cmd+Shift+P (Mac) or Ctrl+Shift+P (Windows/Linux)
Or: Tools menu → Open Omnibar
Copilot AI review requested due to automatic review settings December 7, 2025 08:28
@coderabbitai
Copy link

coderabbitai bot commented Dec 7, 2025

Caution

Review failed

The pull request is closed.

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

Adds a cross-platform Omnibar (command palette) with data providers, search/ranking engine, Compose + IDE UIs, animation system and launch screen, a prefersReducedMotion() API on all targets, UI/theme enhancements, and build updates to publish the xuiper-ui module.

Changes

Cohort / File(s) Summary
Build & Publishing
build.gradle.kts, xuiper-ui/build.gradle.kts
Added :xuiper-ui:publishMavenPublicationToMavenLocal to publishDepsForIdea and updated its description/output; added maven-publish and a MavenPublication for xuiper-ui.
Cross-Platform Reduced Motion API
mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/Platform.kt, mpp-core/src/androidMain/.../Platform.kt, mpp-core/src/iosMain/.../Platform.ios.kt, mpp-core/src/jsMain/.../Platform.js.kt, mpp-core/src/jvmMain/.../Platform.jvm.kt, mpp-core/src/wasmJsMain/.../Platform.wasmJs.kt
Introduced prefersReducedMotion(): Boolean to the shared expect object and platform-specific actual implementations (iOS: accessibility API; JS: matchMedia; JVM: OS heuristics; Android/WASM: fallbacks).
IDEA Omnibar Feature (plugin)
mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/omnibar/IdeaOmnibarAction.kt, .../IdeaOmnibarDataProvider.kt, .../IdeaOmnibarDialog.kt, mpp-idea/src/main/resources/META-INF/plugin.xml
Added IdeaOmnibarAction, IdeaOmnibarDataProvider service, DialogWrapper + Compose omnibar UI, registration in plugin.xml, and action handling (insert/navigate/LLM routing).
Compose Omnibar (UI + Provider)
mpp-ui/src/commonMain/kotlin/.../omnibar/Omnibar.kt, .../OmnibarItem.kt, .../ComposeOmnibarDataProvider.kt, .../OmnibarSearchEngine.kt
Added Omnibar composable, Omnibar data model/types, OmnibarSearchEngine (fuzzy matching, recency/context bonuses), and a Compose Desktop data provider implementation.
VSCode Webview Omnibar
mpp-vscode/webview/src/App.tsx, .../components/Omnibar.tsx, .../Omnibar.css, .../hooks/useVSCode.ts
Integrated Omnibar into webview app: keyboard shortcut, open/close state, message types (getOmnibarItems, omnibarAction, omnibarInsertText, omnibarItems event), UI component, and styles.
Launch Screen & Animation System
mpp-ui/src/commonMain/kotlin/.../launch/XiuperLaunchScreen.kt, .../theme/AutoDevAnimation.kt
Added XiuperLaunchScreen composable with phased animation and reducedMotion support; introduced AutoDevAnimation easing/spring presets and duration constants.
UI Theme & Icons
mpp-ui/src/commonMain/kotlin/.../theme/AutoDevColors.kt, .../icons/AutoDevComposeIcons.kt
Extended color scales and semantic colors (multiple palettes, Text.quaternary, Void additions), added Energy.primary alias and Terminal icon alias.
Main Integration & Omnibar Shortcut
mpp-ui/src/commonMain/kotlin/.../AutoDevApp.kt, mpp-ui/src/jvmMain/kotlin/.../Main.kt
Wire Omnibar into app (FocusRequester, global Cmd/Ctrl+K handling, ComposeOmnibarDataProvider), added --skip-splash handling and conditional XiuperLaunchScreen in Main, propagate reduced-motion preference.
Tests / Docs / Misc
mpp-idea/src/test/kotlin/.../JewelRendererTest.kt, AGENTS.md
Minor test import added and guideline note added to AGENTS.md.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant IDE_Action as IdeaOmnibarAction
    participant Dialog as IdeaOmnibarDialogWrapper
    participant Provider as IdeaOmnibarDataProvider
    participant Search as OmnibarSearchEngine
    participant Host as HostEnvironment

    User->>IDE_Action: Trigger Omnibar (shortcut/menu)
    IDE_Action->>Dialog: open()
    Dialog->>Provider: getItems() / getRecentItems()
    Provider-->>Dialog: items
    User->>Dialog: type query
    Dialog->>Search: search(items, query)
    Search-->>Dialog: rankedResults
    User->>Dialog: select item
    Dialog->>Provider: executeAction(item)
    Provider-->>Dialog: OmnibarActionResult
    alt InsertText
        Dialog->>IDE_Action: insertTextToActiveInput(text)
        IDE_Action->>Host: inject text / forward to editor
    else Navigate
        Dialog->>IDE_Action: navigateToPath(path, line)
        IDE_Action->>Host: open file & set caret
    else LLMQuery
        Dialog->>IDE_Action: sendToLLM(query)
        IDE_Action->>Host: forward query / insert text
    end
    Provider->>Provider: recordUsage(item)
    Dialog->>Dialog: dismiss()
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

  • Attention areas:
    • IdeaOmnibarDialog.kt (Compose state, focus, keyboard handling)
    • Omnibar.kt & OmnibarSearchEngine.kt (search/ranking correctness, performance)
    • Platform.prefersReducedMotion() JVM/JS/iOS implementations (platform APIs and fallbacks)
    • ComposeOmnibarDataProvider & IdeaOmnibarDataProvider (concurrency, recent-items lifecycle)
    • Main.kt / XiuperLaunchScreen integration (reduced-motion behavior, lifecycle transition)
    • VSCode webview message types and duplicate logic in App.tsx (deduplicate event handlers)

Possibly related PRs

Poem

🐇 I hopped through code to plant a bar,
Commands that shimmer, near and far.
Fuzzy searches hum and play,
Motion tamed for peaceful day,
The IDE now leaps — hooray hooray! ✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning, 2 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 40.91% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Linked Issues check ❓ Inconclusive The PR implements most core requirements from issue #495: fuzzy search with weighted ranking [495], wide floating layout (800dp) [495], keyboard navigation [495], and abstracted data provider pattern. However, Gaussian blur background and preview panel are not fully implemented as specified. Verify if Gaussian blur background and preview panel features align with issue #495 acceptance criteria, or if they are intentionally deferred to a future iteration.
Out of Scope Changes check ❓ Inconclusive Several changes appear tangential to the core Omnibar feature: xuiper-ui build configuration, Platform.prefersReducedMotion() implementations across all targets, Terminal icon alias, animation design system, and enhanced color palette. Clarify if the reduced-motion support, xuiper-ui publishing, animation design system, and color palette extensions are explicitly required by issue #495 or are separate enhancements.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title accurately and concisely describes the main feature: implementing an Omnibar command palette for both Compose and IDEA platforms.

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 83dd592 and 6be9873.

📒 Files selected for processing (7)
  • AGENTS.md (1 hunks)
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/AutoDevApp.kt (5 hunks)
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/omnibar/ComposeOmnibarDataProvider.kt (1 hunks)
  • mpp-vscode/webview/src/App.tsx (3 hunks)
  • mpp-vscode/webview/src/components/Omnibar.css (1 hunks)
  • mpp-vscode/webview/src/components/Omnibar.tsx (1 hunks)
  • mpp-vscode/webview/src/hooks/useVSCode.ts (2 hunks)

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: 5

🧹 Nitpick comments (13)
mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/launch/XiuperLaunchScreen.kt (2)

65-85: Consider returning early after onFinished() in normal flow to prevent potential state updates.

In the reduced motion path (line 68), the function returns early after onFinished(). However, in the normal animation sequence (lines 72-84), if onFinished() triggers recomposition that removes this composable, subsequent phase assignments could occur on a disposed state. While this is generally safe due to remember, consider adding a check or using isActive for robustness.

Additionally, phase state updates happen outside the animation spec timing, which could cause minor timing mismatches between the phase transitions and the animateFloatAsState animations.


149-170: Continuous animation loops may not terminate reliably.

The while (phase < 5) conditions read the phase state variable, but since phase is a MutableState and these are coroutine loops, the loops depend on Compose's snapshot system to observe changes. While this typically works, it's fragile.

A more robust pattern would be to use snapshotFlow:

 LaunchedEffect(phase) {
     if (phase >= 1 && phase < 5) {
-        var time = 0f
-        while (phase < 5) {
-            time += 0.03f
-            glowPulse = (sin(time * 2) * 0.25f + 0.75f)
-            delay(16)
-        }
+        snapshotFlow { phase }.collectLatest { currentPhase ->
+            if (currentPhase >= 1 && currentPhase < 5) {
+                var time = 0f
+                while (true) {
+                    time += 0.03f
+                    glowPulse = (sin(time * 2) * 0.25f + 0.75f)
+                    delay(16)
+                }
+            }
+        }
     }
 }

Alternatively, consider using Animatable with animateTo using infiniteRepeatable for these continuous effects, which integrates better with Compose's animation system.

mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/omnibar/OmnibarItem.kt (1)

144-150: Consider logging or documenting the no-op behavior of DefaultOmnibarDataProvider.

The default implementation silently succeeds for executeAction and does nothing for recordUsage. This is fine for previews/testing, but consumers might accidentally use this in production. Consider adding a KDoc note clarifying this is for testing/preview purposes only.

 /**
- * Default implementation that returns empty results.
+ * Default implementation that returns empty results.
+ * Intended for testing, previews, or as a fallback when no platform-specific provider is available.
  */
 object DefaultOmnibarDataProvider : OmnibarDataProvider {
mpp-core/src/jvmMain/kotlin/cc/unitmesh/agent/Platform.jvm.kt (2)

60-72: Consider caching the macOS reduced motion result.

Spawning an external process (defaults read) on every call could be expensive if prefersReducedMotion() is called frequently (e.g., during animation setup). Consider caching the result with a reasonable TTL or computing it once at startup.

+    private var cachedReducedMotion: Boolean? = null
+    private var cachedReducedMotionTime: Long = 0
+    private const val CACHE_TTL_MS = 60_000L // 1 minute
+
     actual fun prefersReducedMotion(): Boolean {
+        val now = System.currentTimeMillis()
+        cachedReducedMotion?.let { cached ->
+            if (now - cachedReducedMotionTime < CACHE_TTL_MS) return cached
+        }
+        
         return try {
             val osName = getOSName().lowercase()
-            when {
+            val result = when {
                 osName.contains("mac") -> {
                     // ... existing macOS logic
                 }
                 // ... rest of the when
             }
+            cachedReducedMotion = result
+            cachedReducedMotionTime = now
+            result
         } catch (e: Exception) {
             false
         }
     }

79-83: Linux implementation could be enhanced to check GNOME accessibility settings.

The current implementation only checks GTK_ENABLE_ANIMATIONS. For broader coverage, consider also checking GNOME's accessibility settings via gsettings:

// Additional check for GNOME desktop
// gsettings get org.gnome.desktop.interface enable-animations

However, the current implementation is a reasonable starting point, and the false default is safe.

mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/omnibar/IdeaOmnibarDataProvider.kt (2)

114-134: Consider wrapping index access in a read action.

FilenameIndex.processAllFileNames accesses the PSI/index which typically requires a read action. While it may work in some contexts, explicit wrapping ensures thread safety:

     suspend fun searchFiles(query: String): List<OmnibarItem> = withContext(Dispatchers.Default) {
         if (query.length < 2) return@withContext emptyList()
         
         val scope = GlobalSearchScope.projectScope(project)
         val files = mutableListOf<OmnibarItem>()
         
         try {
+            com.intellij.openapi.application.runReadAction {
             FilenameIndex.processAllFileNames({ fileName ->
                 // ...
             }, scope, null)
+            }
         } catch (e: Exception) {
             // Ignore indexing errors
         }

93-109: Consider logging exceptions in getCustomCommands for debuggability.

Silently swallowing all exceptions makes it difficult to diagnose issues when custom commands fail to load.

         } catch (e: Exception) {
+            // Log for debugging, but don't fail the entire items load
+            com.intellij.openapi.diagnostic.Logger.getInstance(IdeaOmnibarDataProvider::class.java)
+                .warn("Failed to load custom commands", e)
             emptyList()
         }
mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/omnibar/Omnibar.kt (2)

286-300: Use stable keys in LazyColumn for better performance.

Using items(items.size) with index-based access loses the benefits of key-based diffing. When items are reordered or filtered, Compose may unnecessarily recompose items.

-                items(items.size) { index ->
-                    val item = items[index]
+                items(
+                    items = items,
+                    key = { it.id }
+                ) { item ->
+                    val index = items.indexOf(item)
                     OmnibarResultItem(
                         item = item,
                         isSelected = index == selectedIndex,

Or better, pass isSelected based on item identity rather than index.


48-55: Consider extracting duplicated action execution logic.

The action execution pattern (recordUsage → executeAction → onActionResult → onDismiss) is duplicated in both OmnibarSearchField.onKeyEvent (lines 150-155) and OmnibarResultsList.onItemClick (lines 171-176).

Extract a helper function:

private fun CoroutineScope.executeOmnibarAction(
    item: OmnibarItem,
    dataProvider: OmnibarDataProvider,
    onActionResult: (OmnibarActionResult) -> Unit,
    onDismiss: () -> Unit
) {
    launch {
        dataProvider.recordUsage(item)
        val result = dataProvider.executeAction(item)
        onActionResult(result)
        onDismiss()
    }
}

Also applies to: 139-160

mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/omnibar/IdeaOmnibarAction.kt (3)

78-89: Editor may not scroll to reveal the target line after caret movement.

After moving the caret to the target line, the editor view may not update to show that line. Consider using scrollToCaret to ensure visibility.

         if (line > 0) {
             val editor = fileEditorManager.selectedTextEditor
-            editor?.caretModel?.moveToLogicalPosition(
-                com.intellij.openapi.editor.LogicalPosition(line - 1, 0)
-            )
+            editor?.let {
+                it.caretModel.moveToLogicalPosition(
+                    com.intellij.openapi.editor.LogicalPosition(line - 1, 0)
+                )
+                it.scrollingModel.scrollToCaret(com.intellij.openapi.editor.ScrollType.CENTER)
+            }
         }

53-62: Placeholder cases lack implementation or logging.

The Success, Error, and ShowSubmenu cases are empty placeholders. For Error, users should receive feedback. For ShowSubmenu, the relevant snippet shows it carries items: List<OmnibarItem> which could re-open the Omnibar with filtered results.

Consider at minimum logging or showing a notification for errors:

is OmnibarActionResult.Error -> {
    com.intellij.openapi.ui.Messages.showErrorDialog(
        project, result.message, "Omnibar Error"
    )
}

91-94: sendToLLM is a placeholder that doesn't integrate with LLM services.

The current implementation just inserts text, which doesn't fulfill the LLMQuery intent. Consider adding a TODO comment or opening an issue to track proper LLM integration.

Would you like me to open an issue to track the proper LLM integration for OmnibarActionResult.LLMQuery?

mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/omnibar/IdeaOmnibarDialog.kt (1)

116-118: Arbitrary delay before focus request may be unreliable.

The 100ms delay before requestFocus() is a timing workaround that may fail on slower systems or succeed too late on faster ones. Consider using a more deterministic approach.

-        delay(100)
-        try { focusRequester.requestFocus() } catch (_: Exception) {}
+        // Request focus after composition is complete
+        kotlinx.coroutines.yield()
+        try { focusRequester.requestFocus() } catch (_: Exception) {}

Or use SideEffect / DisposableEffect for focus management.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f6466ad and 83dd592.

📒 Files selected for processing (21)
  • build.gradle.kts (1 hunks)
  • mpp-core/src/androidMain/kotlin/cc/unitmesh/agent/Platform.kt (1 hunks)
  • mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/Platform.kt (1 hunks)
  • mpp-core/src/iosMain/kotlin/cc/unitmesh/agent/Platform.ios.kt (2 hunks)
  • mpp-core/src/jsMain/kotlin/cc/unitmesh/agent/Platform.js.kt (1 hunks)
  • mpp-core/src/jvmMain/kotlin/cc/unitmesh/agent/Platform.jvm.kt (1 hunks)
  • mpp-core/src/wasmJsMain/kotlin/cc/unitmesh/agent/Platform.wasmJs.kt (1 hunks)
  • mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/omnibar/IdeaOmnibarAction.kt (1 hunks)
  • mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/omnibar/IdeaOmnibarDataProvider.kt (1 hunks)
  • mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/omnibar/IdeaOmnibarDialog.kt (1 hunks)
  • mpp-idea/src/main/resources/META-INF/plugin.xml (2 hunks)
  • mpp-idea/src/test/kotlin/cc/unitmesh/devins/idea/renderer/JewelRendererTest.kt (1 hunks)
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/icons/AutoDevComposeIcons.kt (1 hunks)
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/launch/XiuperLaunchScreen.kt (1 hunks)
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/omnibar/Omnibar.kt (1 hunks)
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/omnibar/OmnibarItem.kt (1 hunks)
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/omnibar/OmnibarSearchEngine.kt (1 hunks)
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/theme/AutoDevAnimation.kt (1 hunks)
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/theme/AutoDevColors.kt (5 hunks)
  • mpp-ui/src/jvmMain/kotlin/cc/unitmesh/devins/ui/Main.kt (3 hunks)
  • xuiper-ui/build.gradle.kts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.kt

📄 CodeRabbit inference engine (AGENTS.md)

**/*.kt: Use expect/actual for platform-specific code in Kotlin Multiplatform projects
For @JsExport in Kotlin, use concrete classes (not interfaces) and Promise (not Flow) for JavaScript interoperability
Avoid emoji and UTF-8 characters in WASM code

Files:

  • mpp-core/src/wasmJsMain/kotlin/cc/unitmesh/agent/Platform.wasmJs.kt
  • mpp-ui/src/jvmMain/kotlin/cc/unitmesh/devins/ui/Main.kt
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/theme/AutoDevAnimation.kt
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/icons/AutoDevComposeIcons.kt
  • mpp-core/src/iosMain/kotlin/cc/unitmesh/agent/Platform.ios.kt
  • mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/Platform.kt
  • mpp-core/src/jsMain/kotlin/cc/unitmesh/agent/Platform.js.kt
  • mpp-core/src/androidMain/kotlin/cc/unitmesh/agent/Platform.kt
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/omnibar/OmnibarSearchEngine.kt
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/launch/XiuperLaunchScreen.kt
  • mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/omnibar/IdeaOmnibarAction.kt
  • mpp-core/src/jvmMain/kotlin/cc/unitmesh/agent/Platform.jvm.kt
  • mpp-idea/src/test/kotlin/cc/unitmesh/devins/idea/renderer/JewelRendererTest.kt
  • mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/omnibar/IdeaOmnibarDataProvider.kt
  • mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/omnibar/IdeaOmnibarDialog.kt
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/omnibar/Omnibar.kt
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/omnibar/OmnibarItem.kt
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/theme/AutoDevColors.kt
**/*Compose*.kt

📄 CodeRabbit inference engine (AGENTS.md)

For Compose UI design, use AutoDevColors or MaterialTheme.colorScheme

Files:

  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/icons/AutoDevComposeIcons.kt
🧠 Learnings (5)
📚 Learning: 2025-12-06T13:54:33.558Z
Learnt from: CR
Repo: phodal/auto-dev PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-06T13:54:33.558Z
Learning: Applies to **/*Compose*.kt : For Compose UI design, use `AutoDevColors` or `MaterialTheme.colorScheme`

Applied to files:

  • mpp-ui/src/jvmMain/kotlin/cc/unitmesh/devins/ui/Main.kt
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/theme/AutoDevAnimation.kt
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/launch/XiuperLaunchScreen.kt
  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/theme/AutoDevColors.kt
📚 Learning: 2025-12-06T13:54:33.558Z
Learnt from: CR
Repo: phodal/auto-dev PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-06T13:54:33.558Z
Learning: Applies to **/*.kt : Use `expect`/`actual` for platform-specific code in Kotlin Multiplatform projects

Applied to files:

  • mpp-core/src/iosMain/kotlin/cc/unitmesh/agent/Platform.ios.kt
  • mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/Platform.kt
  • mpp-core/src/androidMain/kotlin/cc/unitmesh/agent/Platform.kt
  • mpp-core/src/jvmMain/kotlin/cc/unitmesh/agent/Platform.jvm.kt
📚 Learning: 2025-12-06T13:54:33.558Z
Learnt from: CR
Repo: phodal/auto-dev PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-06T13:54:33.558Z
Learning: Applies to **/*Renderer.kt : When modifying `CodingAgentRenderer` in Kotlin, update ALL implementations: `DefaultCodingAgentRenderer`, `ComposeRenderer`, `JewelRenderer`, `ServerSideRenderer`, `JsRendererAdapter`

Applied to files:

  • mpp-idea/src/test/kotlin/cc/unitmesh/devins/idea/renderer/JewelRendererTest.kt
📚 Learning: 2025-12-06T13:54:33.558Z
Learnt from: CR
Repo: phodal/auto-dev PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-06T13:54:33.558Z
Learning: Run `./gradlew :mpp-ui:generateI18n4kFiles` after i18n changes

Applied to files:

  • build.gradle.kts
📚 Learning: 2025-12-06T13:54:33.558Z
Learnt from: CR
Repo: phodal/auto-dev PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-06T13:54:33.558Z
Learning: Applies to {**/Compose*.kt,mpp-ui/src/jsMain/typescript/**/*.ts} : Never use hardcoded colors - always use design tokens from the design system

Applied to files:

  • mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/theme/AutoDevColors.kt
🧬 Code graph analysis (4)
mpp-ui/src/jvmMain/kotlin/cc/unitmesh/devins/ui/Main.kt (4)
mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/theme/AutoDevTheme.kt (2)
  • AutoDevTheme (114-141)
  • AutoDevTheme (146-159)
mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/launch/XiuperLaunchScreen.kt (1)
  • XiuperLaunchScreen (56-301)
mpp-ui/src/jvmMain/kotlin/cc/unitmesh/devins/ui/desktop/DesktopWindowLayout.kt (1)
  • DesktopWindowLayout (24-46)
mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/AutoDevApp.kt (1)
  • AutoDevApp (34-68)
mpp-core/src/jvmMain/kotlin/cc/unitmesh/agent/Platform.jvm.kt (6)
mpp-core/src/wasmJsMain/kotlin/cc/unitmesh/agent/Platform.wasmJs.kt (1)
  • getOSName (13-15)
mpp-core/src/iosMain/kotlin/cc/unitmesh/agent/Platform.ios.kt (1)
  • getOSName (19-21)
mpp-core/src/androidMain/kotlin/cc/unitmesh/agent/Platform.kt (1)
  • getOSName (15-17)
mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/Platform.kt (1)
  • getOSName (14-14)
mpp-core/src/jsMain/kotlin/cc/unitmesh/agent/Platform.js.kt (1)
  • getOSName (13-15)
mpp-core/src/jsMain/kotlin/cc/unitmesh/agent/PlatformExports.kt (1)
  • getOSName (21-21)
mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/omnibar/IdeaOmnibarDialog.kt (1)
mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/omnibar/OmnibarItem.kt (1)
  • items (108-108)
mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/omnibar/Omnibar.kt (2)
mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/omnibar/IdeaOmnibarDialog.kt (1)
  • handleKeyEvent (135-164)
mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/omnibar/OmnibarItem.kt (1)
  • items (108-108)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Agent
  • GitHub Check: Build
  • GitHub Check: Test mpp-core (KMP)
🔇 Additional comments (34)
mpp-idea/src/test/kotlin/cc/unitmesh/devins/idea/renderer/JewelRendererTest.kt (1)

5-5: TaskStatus import correctly supports task status assertions

The added TaskStatus import matches its usage in testTaskBoundaryToolUpdatesTaskList and keeps the test readable; no further changes needed here.

xuiper-ui/build.gradle.kts (1)

4-4: Maven publishing configuration is correct and well-integrated.

The publication name "maven" properly derives the task name publishMavenPublicationToMavenLocal that the root build references. The kotlin("jvm") plugin provides the java component needed for line 13.

Also applies to: 10-17

build.gradle.kts (1)

30-30: Task integration is consistent and correct.

The new dependency on :xuiper-ui:publishMavenPublicationToMavenLocal correctly references the "maven" publication defined in xuiper-ui/build.gradle.kts. Description and success message were properly updated to reflect the expanded scope.

Also applies to: 37-37, 41-41

mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/icons/AutoDevComposeIcons.kt (1)

106-106: Terminal icon addition is correct.

The Icons.Default.Terminal icon is available in the Material Icons library and is already in use elsewhere in the codebase (e.g., Omnibar.kt). The implementation follows the established pattern and is properly placed in the "Tools & Utilities" section.

mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/theme/AutoDevColors.kt (3)

25-28: Well-structured additions for the Omnibar feature.

The new Void colors follow the established progression and provide appropriate semantic tokens for the floating palette UI. The surfaceElevated sits logically between surface1 and surface2, and having border as a semantic alias (even with the same value as surface3) allows independent evolution of border styling later.

Based on learnings, these additions properly extend the design tokens in AutoDevColors for Compose UI usage.


44-44: Good semantic alias.

Exposing primary as an alias to xiu improves API discoverability for developers unfamiliar with the design system's terminology.


86-88: Completes the text hierarchy nicely.

The quaternary text color provides a fourth level for the most subtle text emphasis, useful for disabled states or de-emphasized metadata. The value aligns with Neutral.c600 maintaining consistency across the design system.

mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/launch/XiuperLaunchScreen.kt (2)

172-176: Good use of AutoDevColors for theming.

The implementation correctly uses AutoDevColors.Void.bg, AutoDevColors.Energy.*, and AutoDevColors.Text.secondary throughout, which aligns with the coding guidelines for Compose UI design.


280-298: LGTM!

The logo and tagline text rendering is well-structured. Brand text is appropriately hardcoded as it's not subject to localization.

mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/omnibar/OmnibarItem.kt (2)

8-23: LGTM!

The OmnibarItemType enum provides clear categorization with good documentation for each type. The categories align well with the PR objectives for supporting commands, files, symbols, agents, and settings.


94-112: LGTM!

The OmnibarActionResult sealed class provides a clean, type-safe API for action outcomes. The variants cover the expected use cases (success, text insertion, LLM queries, navigation, submenus, errors).

mpp-core/src/iosMain/kotlin/cc/unitmesh/agent/Platform.ios.kt (1)

48-51: LGTM!

The iOS implementation correctly uses UIAccessibilityIsReduceMotionEnabled() which is the standard iOS accessibility API for detecting reduced motion preference. This follows the expect/actual pattern per coding guidelines.

mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/Platform.kt (1)

30-35: LGTM!

The expect declaration for prefersReducedMotion() is well-documented and follows the expect/actual pattern correctly. The KDoc clearly explains the accessibility purpose, which helps platform implementers understand the expected behavior.

mpp-idea/src/main/resources/META-INF/plugin.xml (1)

60-61: LGTM! Service and action registration follows IntelliJ conventions.

The project-level service registration for IdeaOmnibarDataProvider and the action with keyboard shortcuts are correctly configured. The Cmd/Ctrl+Shift+P shortcut is a familiar pattern for command palettes.

Also applies to: 70-81

mpp-core/src/wasmJsMain/kotlin/cc/unitmesh/agent/Platform.wasmJs.kt (1)

44-50: LGTM! Reasonable default given WASM limitations.

The hardcoded false return is an acceptable fallback since WASM has limited access to browser APIs. The comment correctly explains the js() expression restrictions.

mpp-core/src/androidMain/kotlin/cc/unitmesh/agent/Platform.kt (1)

48-54: Acceptable workaround, but consider documenting the accessibility impact.

The limitation around Context access is accurately documented. Returning false means animations will always play on Android regardless of user's accessibility settings for reduced motion.

For improved accessibility in the future, consider:

  • Passing Context from app initialization
  • Using Application.registerActivityLifecycleCallbacks to capture a Context reference

Based on learnings, the expect/actual pattern is correctly applied here.

mpp-core/src/jsMain/kotlin/cc/unitmesh/agent/Platform.js.kt (1)

66-81: LGTM! Properly detects reduced motion preference in browser.

The implementation correctly:

  • Distinguishes browser from Node.js environments
  • Uses the standard prefers-reduced-motion media query
  • Falls back safely to false on errors
mpp-ui/src/jvmMain/kotlin/cc/unitmesh/devins/ui/Main.kt (3)

36-39: LGTM! Correctly caches prefersReducedMotion() result.

Using remember { Platform.prefersReducedMotion() } ensures the system call is made only once during composition, avoiding repeated calls on recomposition.


64-73: Splash screen uses hardcoded DARK theme.

The splash screen forces ThemeManager.ThemeMode.DARK regardless of user's theme preference. This is likely intentional for visual consistency during launch, but consider documenting this design decision if it differs from the main app theme.


74-139: LGTM! Main UI conditional branch is well-structured.

The DesktopWindowLayout integration with title bar tabs, menu bar, and AutoDevApp follows the established patterns from the relevant code snippets. The state management and callbacks are properly wired.

mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/omnibar/Omnibar.kt (1)

407-445: LGTM! Keyboard navigation logic matches IDEA implementation.

The handleKeyEvent function correctly handles arrow keys for navigation, Enter for selection, and Escape for dismissal. The logic mirrors the implementation in IdeaOmnibarDialog.kt (lines 134-163), ensuring consistent behavior.

mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/omnibar/OmnibarSearchEngine.kt (5)

101-110: Unsafe cast of metadata tags could cause unexpected behavior.

If metadata["tags"] contains a non-List value, the as? List<*> safely returns null. However, if it's a List with non-String elements, tag is String handles it. The logic is defensive but consider documenting the expected metadata format.


83-96: Recency bonus calculation is well-structured with clear decay tiers.

The tiered approach (1h, 6h, 24h, 72h) provides good granularity for recency-based ranking. The early return for lastUsedTimestamp == 0L correctly handles items never used.


117-127: Fuzzy matching implementation is correct and efficient.

The algorithm correctly checks if all query characters appear in order within the string, with case-insensitive comparison. The early return when all characters match is a good optimization.


133-145: fuzzyMatchPositions returns empty list on partial match - ensure callers handle this.

The function returns an empty list if not all query characters are found, which is correct for highlighting purposes. Callers should be aware that a non-empty return indicates a complete match.


63-77: Consider using LinkedHashMap to preserve category insertion order.

groupBy returns a LinkedHashMap which preserves encounter order, so the current implementation should maintain item order. This is good for predictable UI rendering.

mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/omnibar/IdeaOmnibarDialog.kt (4)

106-118: Loading logic and initial setup are well-structured.

The LaunchedEffect(Unit) properly handles async loading with a try-finally for the loading state. Combining recent items with all items and deduplicating by ID is a good approach.


134-164: Keyboard navigation implementation is comprehensive.

The key handler correctly manages Up/Down cycling, Enter for selection, and Escape for dismissal. The wrap-around behavior for navigation is user-friendly.


311-388: Result item rendering with hover and selection states is well-implemented.

The use of hoverable with MutableInteractionSource and LaunchedEffect for hover detection provides smooth interaction. The conditional background colors for selected/hovered states are clear.


150-158: Current implementation is safe; recordUsage and executeAction complete synchronously.

Both recordUsage and executeAction are quick operations—list mutations and immediate result returns with no async I/O—so they complete before onActionResult triggers dialog dismissal. The coroutine scope from rememberCoroutineScope() is properly managed by Compose and won't be cancelled by dialog closure.

However, if these methods are extended to include long-running operations in the future, consider ensuring completion with awaitClose() or similar scope lifecycle management.

mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/theme/AutoDevAnimation.kt (4)

106-116: Launch duration total may not match sum of individual phases.

Duration.Launch.TOTAL is 2200ms, but summing the phases: 100 + 400 + 500 + 300 + 600 + 300 = 2200ms. The math checks out; however, if phases overlap or run in parallel, the comment "总时长" (total duration) should clarify whether this is sequential or includes overlaps.

The durations are well-documented and the total matches the sum. Consider adding a brief note if any phases are meant to overlap.


22-54: Easing curves are well-defined with clear use cases.

The three easing options cover the main interaction patterns: fast UI transitions (EaseXiu), streaming content (EaseStream/LinearEasing), and general-purpose (EaseStandard). The design rationale in Chinese comments is helpful.


66-87: Spring animation presets provide good tactile feedback options.

SpringTactile with moderate damping (0.6f) and stiffness (180f) should provide snappy feedback. SpringGentle using Spring.DampingRatioMediumBouncy and Spring.StiffnessLow is appropriate for smoother transitions.


122-139: Tween factory functions provide clean API for animation specs.

The generic <T> typing and default duration parameters make these functions easy to use. Each function correctly maps to its corresponding easing curve.

Comment on lines +65 to +76
private fun insertTextToActiveInput(project: com.intellij.openapi.project.Project, text: String) {
// Try to find and use AutoInputService or similar
try {
val serviceClass = Class.forName("cc.unitmesh.devti.sketch.AutoInputService")
val getInstance = serviceClass.getMethod("getInstance", com.intellij.openapi.project.Project::class.java)
val service = getInstance.invoke(null, project)
val putText = serviceClass.getMethod("putText", String::class.java)
putText.invoke(service, text)
} catch (e: Exception) {
// Fallback: Could show in a notification or editor
}
}
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

Reflection-based service lookup silently fails without feedback.

The empty catch block swallows all exceptions, making debugging difficult when integration fails. Consider logging the error or showing a notification to the user.

         try {
             val serviceClass = Class.forName("cc.unitmesh.devti.sketch.AutoInputService")
             val getInstance = serviceClass.getMethod("getInstance", com.intellij.openapi.project.Project::class.java)
             val service = getInstance.invoke(null, project)
             val putText = serviceClass.getMethod("putText", String::class.java)
             putText.invoke(service, text)
         } catch (e: Exception) {
-            // Fallback: Could show in a notification or editor
+            // Log for debugging, consider notifying user
+            com.intellij.openapi.diagnostic.Logger.getInstance(IdeaOmnibarAction::class.java)
+                .warn("Failed to insert text via AutoInputService", e)
         }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
private fun insertTextToActiveInput(project: com.intellij.openapi.project.Project, text: String) {
// Try to find and use AutoInputService or similar
try {
val serviceClass = Class.forName("cc.unitmesh.devti.sketch.AutoInputService")
val getInstance = serviceClass.getMethod("getInstance", com.intellij.openapi.project.Project::class.java)
val service = getInstance.invoke(null, project)
val putText = serviceClass.getMethod("putText", String::class.java)
putText.invoke(service, text)
} catch (e: Exception) {
// Fallback: Could show in a notification or editor
}
}
private fun insertTextToActiveInput(project: com.intellij.openapi.project.Project, text: String) {
// Try to find and use AutoInputService or similar
try {
val serviceClass = Class.forName("cc.unitmesh.devti.sketch.AutoInputService")
val getInstance = serviceClass.getMethod("getInstance", com.intellij.openapi.project.Project::class.java)
val service = getInstance.invoke(null, project)
val putText = serviceClass.getMethod("putText", String::class.java)
putText.invoke(service, text)
} catch (e: Exception) {
// Log for debugging, consider notifying user
com.intellij.openapi.diagnostic.Logger.getInstance(IdeaOmnibarAction::class.java)
.warn("Failed to insert text via AutoInputService", e)
}
}

Comment on lines +25 to +26
private val recentlyUsedItems = mutableListOf<OmnibarItem>()
private val maxRecentItems = 10
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

Thread safety concern: recentlyUsedItems accessed without synchronization.

The recentlyUsedItems list is modified in recordUsage() which runs in coroutine scope, but it's also read in getRecentItems(). This could lead to ConcurrentModificationException or inconsistent reads.

Consider using a thread-safe collection:

-    private val recentlyUsedItems = mutableListOf<OmnibarItem>()
+    private val recentlyUsedItems = java.util.Collections.synchronizedList(mutableListOf<OmnibarItem>())

Or wrap access with a mutex for more control over concurrent operations.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
private val recentlyUsedItems = mutableListOf<OmnibarItem>()
private val maxRecentItems = 10
private val recentlyUsedItems = java.util.Collections.synchronizedList(mutableListOf<OmnibarItem>())
private val maxRecentItems = 10

Comment on lines +63 to +73
override suspend fun recordUsage(item: OmnibarItem) {
val updatedItem = item.copy(
lastUsedTimestamp = System.currentTimeMillis(),
type = OmnibarItemType.RECENT
)
recentlyUsedItems.removeAll { it.id == item.id }
recentlyUsedItems.add(0, updatedItem)
if (recentlyUsedItems.size > maxRecentItems) {
recentlyUsedItems.removeAt(recentlyUsedItems.lastIndex)
}
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Bug: Changing type to RECENT breaks action execution for recent items.

When recordUsage stores the item with type = OmnibarItemType.RECENT, subsequent selection of this item from recent list will hit the else branch in executeAction() (line 59) instead of executing the original command logic.

Preserve the original type and use a separate flag or keep original metadata:

     override suspend fun recordUsage(item: OmnibarItem) {
         val updatedItem = item.copy(
             lastUsedTimestamp = System.currentTimeMillis(),
-            type = OmnibarItemType.RECENT
+            metadata = item.metadata + ("originalType" to item.type.name)
         )
         recentlyUsedItems.removeAll { it.id == item.id }
         recentlyUsedItems.add(0, updatedItem)

Then in executeAction, handle items by their original type stored in metadata.

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In
mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/omnibar/IdeaOmnibarDataProvider.kt
around lines 63–73, recordUsage currently overwrites the item's type to
OmnibarItemType.RECENT which breaks action execution; instead preserve the
original type and mark the item as recent by adding or updating a separate
flag/metadata field (e.g., isRecent = true or recentTimestamp in item.metadata)
and set lastUsedTimestamp without mutating type. Update recordUsage to
remove/insert the updated item with the preserved type and new metadata, and
update executeAction to determine the action to run from the original type (or
metadata) rather than the RECENT marker.

Comment on lines +222 to +226
) {
Text(
text = "\uD83D\uDD0D", // 🔍
style = TextStyle(fontSize = 16.sp)
)
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

Emoji characters may cause issues in WASM targets.

Per coding guidelines, avoid emoji and UTF-8 characters in WASM code. The search icon (🔍) and item type icons (📄, 🤖) use emoji. Consider using icon components or ASCII alternatives for cross-platform compatibility.

         Text(
-            text = "\uD83D\uDD0D", // 🔍
+            text = "⌕", // Search symbol (or use an Icon composable)
             style = TextStyle(fontSize = 16.sp)
         )

For item icons, consider extracting to a helper or using Jewel icons:

OmnibarItemType.FILE -> "📄"  // Consider: "◉" or Icon component
OmnibarItemType.AGENT -> "🤖" // Consider: "◈" or Icon component

Also applies to: 343-356

🤖 Prompt for AI Agents
In mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/omnibar/IdeaOmnibarDialog.kt
around lines 222-226 (and similarly 343-356), the code uses emoji characters
(e.g., "🔍", "📄", "🤖") which break WASM targets; replace these emoji usages
with cross-platform alternatives by using an icon component (preferred) or plain
ASCII/symbol fallbacks (e.g., "◉", "◈", or an appropriate Jewel/icon
composable), and extract item-type icon logic into a small helper function that
maps OmnibarItemType to the chosen icon component or ASCII string so all places
use the same platform-safe representation.

Comment on lines +70 to +87
LaunchedEffect(visible) {
if (visible) {
isLoading = true
try {
val items = dataProvider.getItems()
val recentItems = dataProvider.getRecentItems()
allItems = (recentItems + items).distinctBy { it.id }
filteredItems = searchEngine.search(allItems, "")
} finally {
isLoading = false
}
delay(100)
try { focusRequester.requestFocus() } catch (_: Exception) {}
} else {
searchQuery = ""
selectedIndex = 0
}
}
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

State reset logic may not execute on dismiss.

The else block (lines 83-86) that resets searchQuery and selectedIndex is inside LaunchedEffect(visible). When visible becomes false, the composable returns early at line 56, so this LaunchedEffect won't run to reset state.

Consider moving reset logic outside, or handle it when the dialog reopens:

     LaunchedEffect(visible) {
         if (visible) {
+            // Reset state on open
+            searchQuery = ""
+            selectedIndex = 0
             isLoading = true
             // ... rest of loading logic
-        } else {
-            searchQuery = ""
-            selectedIndex = 0
         }
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
LaunchedEffect(visible) {
if (visible) {
isLoading = true
try {
val items = dataProvider.getItems()
val recentItems = dataProvider.getRecentItems()
allItems = (recentItems + items).distinctBy { it.id }
filteredItems = searchEngine.search(allItems, "")
} finally {
isLoading = false
}
delay(100)
try { focusRequester.requestFocus() } catch (_: Exception) {}
} else {
searchQuery = ""
selectedIndex = 0
}
}
LaunchedEffect(visible) {
if (visible) {
// Reset state on open
searchQuery = ""
selectedIndex = 0
isLoading = true
try {
val items = dataProvider.getItems()
val recentItems = dataProvider.getRecentItems()
allItems = (recentItems + items).distinctBy { it.id }
filteredItems = searchEngine.search(allItems, "")
} finally {
isLoading = false
}
delay(100)
try { focusRequester.requestFocus() } catch (_: Exception) {}
}
}

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 implements a comprehensive Omnibar command palette feature inspired by Linear and Raycast, adding a keyboard-driven command interface across Compose Multiplatform and IntelliJ IDEA platforms. The implementation introduces a fuzzy search engine with weighted ranking, launch screen animations, accessibility support through reduced motion detection, and platform-specific integrations.

Key Changes

  • Omnibar Command Palette: 800dp wide floating interface with fuzzy search, keyboard navigation (Up/Down/Enter/Escape), and weighted result ranking based on recency, context, match quality, and base weight
  • Platform Accessibility API: Added Platform.prefersReducedMotion() across all platforms (JVM, iOS, Android, JS, WASM) to support users with motion sensitivity
  • Launch Screen Animation: Implemented Xiuper-branded launch animation with accessibility-aware simplified mode

Reviewed changes

Copilot reviewed 20 out of 21 changed files in this pull request and generated 11 comments.

Show a summary per file
File Description
xuiper-ui/build.gradle.kts Added maven-publish plugin for local publishing to support mpp-idea integration
build.gradle.kts Updated publishDepsForIdea task to include xuiper-ui dependency
mpp-core/src/.../Platform.kt Added prefersReducedMotion() API with platform-specific implementations for accessibility
mpp-ui/.../omnibar/OmnibarItem.kt Core data models: OmnibarItem with type enum, OmnibarActionResult sealed class, OmnibarDataProvider interface
mpp-ui/.../omnibar/OmnibarSearchEngine.kt Fuzzy search engine with weighted ranking algorithm (recency, context, match quality)
mpp-ui/.../omnibar/Omnibar.kt Main Compose UI component with 800dp floating layout, keyboard navigation, loading states
mpp-ui/.../launch/XiuperLaunchScreen.kt Brand launch animation with phase-based timing, light effects, reduced motion support
mpp-ui/.../theme/AutoDevAnimation.kt Animation system defining EaseXiu (fast UI), EaseStream (linear AI), and timing constants
mpp-ui/.../theme/AutoDevColors.kt Added surfaceElevated, surfaceHover, border colors for Omnibar styling
mpp-ui/.../icons/AutoDevComposeIcons.kt Added Terminal icon for Omnibar command type display
mpp-ui/src/jvmMain/.../Main.kt Integrated splash screen with --skip-splash flag and reduced motion detection
mpp-idea/.../omnibar/IdeaOmnibarDataProvider.kt IDEA-specific provider implementing built-in commands, custom commands, file search, recent items tracking
mpp-idea/.../omnibar/IdeaOmnibarDialog.kt DialogWrapper with JewelComposePanel for native IDE integration using Jewel theme
mpp-idea/.../omnibar/IdeaOmnibarAction.kt AnAction for triggering Omnibar with action result handlers (navigation, text insertion, LLM queries)
mpp-idea/.../plugin.xml Registered Omnibar action with Ctrl/Cmd+Shift+P shortcuts and Tools menu entry
mpp-idea/.../JewelRendererTest.kt Added missing TaskStatus import (unrelated import fix)

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

onConfigureRemote = {
uiState.showRemoteConfigDialog = true
AutoDevLogger.info("AutoDevMain") { "☁️ Configure Remote" }
// 显示启动动画或主界面
Copy link

Copilot AI Dec 7, 2025

Choose a reason for hiding this comment

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

Comments should be in English for consistency with the rest of the codebase.

Suggested translations:

  • Line 36: "Splash screen state"
  • Line 63: "Show splash screen or main interface"
Suggested change
// 显示启动动画或主界面
// Show splash screen or main interface

Copilot uses AI. Check for mistakes.
// 已由全局状态管理,无需额外操作
},
onSidebarVisibilityChanged = { visible ->
// 已由全局状态管理,无需额外操作
Copy link

Copilot AI Dec 7, 2025

Choose a reason for hiding this comment

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

Comments should be in English for consistency with the rest of the codebase.

Suggested translation: "Already managed by global state, no additional action needed"

Suggested change
// 已由全局状态管理,无需额外操作
// Already managed by global state, no additional action needed

Copilot uses AI. Check for mistakes.
Comment on lines +25 to +73
private val recentlyUsedItems = mutableListOf<OmnibarItem>()
private val maxRecentItems = 10

override suspend fun getItems(): List<OmnibarItem> = withContext(Dispatchers.Default) {
val items = mutableListOf<OmnibarItem>()

// Add built-in commands
items.addAll(getBuiltinCommands())

// Add custom commands from project
items.addAll(getCustomCommands())

items
}

override suspend fun getRecentItems(): List<OmnibarItem> {
return recentlyUsedItems.toList()
}

override suspend fun executeAction(item: OmnibarItem): OmnibarActionResult {
return when (item.type) {
OmnibarItemType.COMMAND, OmnibarItemType.CUSTOM_COMMAND -> {
val commandText = "/${item.metadata["commandName"] ?: item.title}"
OmnibarActionResult.InsertText(commandText)
}
OmnibarItemType.FILE -> {
val path = item.metadata["path"] as? String ?: return OmnibarActionResult.Error("No file path")
OmnibarActionResult.Navigate(path)
}
OmnibarItemType.SYMBOL -> {
val path = item.metadata["path"] as? String ?: return OmnibarActionResult.Error("No symbol path")
val line = item.metadata["line"] as? Int ?: 0
OmnibarActionResult.Navigate(path, line)
}
else -> OmnibarActionResult.Success("Action executed: ${item.title}")
}
}

override suspend fun recordUsage(item: OmnibarItem) {
val updatedItem = item.copy(
lastUsedTimestamp = System.currentTimeMillis(),
type = OmnibarItemType.RECENT
)
recentlyUsedItems.removeAll { it.id == item.id }
recentlyUsedItems.add(0, updatedItem)
if (recentlyUsedItems.size > maxRecentItems) {
recentlyUsedItems.removeAt(recentlyUsedItems.lastIndex)
}
}
Copy link

Copilot AI Dec 7, 2025

Choose a reason for hiding this comment

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

The recentlyUsedItems list is mutable and accessed without synchronization. Since recordUsage is a suspend function and can be called concurrently, this could lead to race conditions. Consider using a thread-safe collection like CopyOnWriteArrayList or adding synchronization to ensure thread safety.

Copilot uses AI. Check for mistakes.
Comment on lines +15 to +16
* Registered shortcuts:
* - Cmd+K (macOS) / Ctrl+K (Windows/Linux)
Copy link

Copilot AI Dec 7, 2025

Choose a reason for hiding this comment

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

The documentation mentions "Cmd+K (macOS) / Ctrl+K (Windows/Linux)" as a registered shortcut, but the plugin.xml only registers "ctrl shift P" / "meta shift P". Either update the documentation to remove the Cmd+K/Ctrl+K reference, or add the keyboard shortcuts in plugin.xml if this was intended.

Suggested change
* Registered shortcuts:
* - Cmd+K (macOS) / Ctrl+K (Windows/Linux)
* Registered shortcut:

Copilot uses AI. Check for mistakes.
Comment on lines +1 to +146
package cc.unitmesh.devins.ui.compose.omnibar

import cc.unitmesh.agent.Platform

/**
* Search engine for Omnibar with fuzzy matching and weighted ranking.
*
* Ranking factors (in priority order):
* 1. Recency - recently used items get higher priority
* 2. Context relevance - items matching current context (open file type, etc.)
* 3. Match quality - exact > prefix > contains > fuzzy
* 4. Base weight - configured priority of item type
*/
class OmnibarSearchEngine {

companion object {
private const val RECENCY_WEIGHT = 100
private const val RECENCY_DECAY_HOURS = 24
private const val MAX_RESULTS = 50
}

/**
* Search and rank items based on query and context.
*
* @param items All available items
* @param query Search query (can be empty for default ranking)
* @param contextTags Optional tags for context-aware ranking (e.g., "kotlin", "test")
* @return Ranked and filtered list of items
*/
fun search(
items: List<OmnibarItem>,
query: String,
contextTags: Set<String> = emptySet()
): List<OmnibarItem> {
if (query.isBlank()) {
// No query - return items ranked by recency and weight
return items
.map { it to calculateRecencyBonus(it) }
.sortedByDescending { (item, recencyBonus) ->
item.weight + recencyBonus + contextBonus(item, contextTags)
}
.take(MAX_RESULTS)
.map { it.first }
}

// With query - filter and rank by match quality
return items
.map { item ->
val matchScore = item.matchScore(query)
val recencyBonus = calculateRecencyBonus(item)
val contextScore = contextBonus(item, contextTags)
item to (matchScore + recencyBonus + contextScore)
}
.filter { it.second > 0 }
.sortedByDescending { it.second }
.take(MAX_RESULTS)
.map { it.first }
}

/**
* Group items by category for display.
*/
fun groupByCategory(items: List<OmnibarItem>): Map<String, List<OmnibarItem>> {
return items.groupBy { item ->
item.category.ifBlank {
when (item.type) {
OmnibarItemType.COMMAND -> "Commands"
OmnibarItemType.CUSTOM_COMMAND -> "Custom Commands"
OmnibarItemType.FILE -> "Files"
OmnibarItemType.SYMBOL -> "Symbols"
OmnibarItemType.AGENT -> "Agents"
OmnibarItemType.RECENT -> "Recent"
OmnibarItemType.SETTING -> "Settings"
}
}
}
}

/**
* Calculate recency bonus based on last used timestamp.
* Items used recently get higher scores, with exponential decay.
*/
private fun calculateRecencyBonus(item: OmnibarItem): Int {
if (item.lastUsedTimestamp == 0L) return 0

val currentTime = Platform.getCurrentTimestamp()
val ageHours = (currentTime - item.lastUsedTimestamp) / (1000L * 60 * 60)

return when {
ageHours < 1 -> RECENCY_WEIGHT * 4 // Last hour
ageHours < 6 -> RECENCY_WEIGHT * 2 // Last 6 hours
ageHours < RECENCY_DECAY_HOURS -> RECENCY_WEIGHT // Last 24 hours
ageHours < 72 -> RECENCY_WEIGHT / 2 // Last 3 days
else -> 0
}
}

/**
* Calculate context bonus for items matching current context.
*/
private fun contextBonus(item: OmnibarItem, contextTags: Set<String>): Int {
if (contextTags.isEmpty()) return 0

val itemTags = item.metadata["tags"] as? List<*> ?: return 0
val matchCount = itemTags.count { tag ->
tag is String && contextTags.contains(tag.lowercase())
}

return matchCount * 50
}
}

/**
* Extension function for fuzzy matching on strings.
* Returns true if all characters in query appear in text in order.
*/
fun String.fuzzyContains(query: String): Boolean {
if (query.isEmpty()) return true
var queryIndex = 0
for (char in this) {
if (char.lowercaseChar() == query[queryIndex].lowercaseChar()) {
queryIndex++
if (queryIndex == query.length) return true
}
}
return false
}

/**
* Get fuzzy match positions for highlighting.
* Returns list of character indices that match the query.
*/
fun String.fuzzyMatchPositions(query: String): List<Int> {
if (query.isEmpty()) return emptyList()
val positions = mutableListOf<Int>()
var queryIndex = 0
for ((index, char) in this.withIndex()) {
if (queryIndex < query.length &&
char.lowercaseChar() == query[queryIndex].lowercaseChar()) {
positions.add(index)
queryIndex++
}
}
return if (queryIndex == query.length) positions else emptyList()
}

Copy link

Copilot AI Dec 7, 2025

Choose a reason for hiding this comment

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

The new Omnibar search engine and fuzzy matching logic (OmnibarSearchEngine, fuzzy matching extensions) lack test coverage. Given that there are existing test directories (e.g., mpp-ui/src/commonTest/kotlin/cc/unitmesh/devins/ui/compose/) and other similar components have tests, consider adding unit tests to verify:

  • Fuzzy matching correctness (fuzzyContains, fuzzyMatchPositions)
  • Match score calculations
  • Recency bonus calculations
  • Context bonus calculations
  • Search ranking behavior

Copilot uses AI. Check for mistakes.
override suspend fun recordUsage(item: OmnibarItem) {
val updatedItem = item.copy(
lastUsedTimestamp = System.currentTimeMillis(),
type = OmnibarItemType.RECENT
Copy link

Copilot AI Dec 7, 2025

Choose a reason for hiding this comment

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

The recordUsage method modifies the item type to RECENT when recording usage (line 66). This causes an issue because when a user uses the same command again from recent items, it will be stored with type RECENT instead of preserving its original type (e.g., COMMAND or CUSTOM_COMMAND). Consider preserving the original type or adding a separate flag to indicate if an item appears in recent history.

Suggested change
type = OmnibarItemType.RECENT
isRecent = true

Copilot uses AI. Check for mistakes.
Comment on lines +78 to +89
private fun navigateToPath(project: com.intellij.openapi.project.Project, path: String, line: Int) {
val file = LocalFileSystem.getInstance().findFileByPath(path) ?: return
val fileEditorManager = FileEditorManager.getInstance(project)
fileEditorManager.openFile(file, true)

if (line > 0) {
val editor = fileEditorManager.selectedTextEditor
editor?.caretModel?.moveToLogicalPosition(
com.intellij.openapi.editor.LogicalPosition(line - 1, 0)
)
}
}
Copy link

Copilot AI Dec 7, 2025

Choose a reason for hiding this comment

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

The navigateToPath function attempts to open a file without validating whether the file exists or is accessible. If the file doesn't exist, findFileByPath returns null and the function silently returns. Consider adding error handling or notification to inform the user when file navigation fails, especially since this is triggered from user actions in the Omnibar.

Copilot uses AI. Check for mistakes.
Comment on lines +65 to +76
private fun insertTextToActiveInput(project: com.intellij.openapi.project.Project, text: String) {
// Try to find and use AutoInputService or similar
try {
val serviceClass = Class.forName("cc.unitmesh.devti.sketch.AutoInputService")
val getInstance = serviceClass.getMethod("getInstance", com.intellij.openapi.project.Project::class.java)
val service = getInstance.invoke(null, project)
val putText = serviceClass.getMethod("putText", String::class.java)
putText.invoke(service, text)
} catch (e: Exception) {
// Fallback: Could show in a notification or editor
}
}
Copy link

Copilot AI Dec 7, 2025

Choose a reason for hiding this comment

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

The insertTextToActiveInput method uses reflection to dynamically call AutoInputService. While this provides flexibility, the catch block silently swallows all exceptions without logging or user feedback. Consider at minimum logging the exception for debugging purposes, or providing user feedback when text insertion fails.

Copilot uses AI. Check for mistakes.
Comment on lines +60 to +77
/**
* Group items by category for display.
*/
fun groupByCategory(items: List<OmnibarItem>): Map<String, List<OmnibarItem>> {
return items.groupBy { item ->
item.category.ifBlank {
when (item.type) {
OmnibarItemType.COMMAND -> "Commands"
OmnibarItemType.CUSTOM_COMMAND -> "Custom Commands"
OmnibarItemType.FILE -> "Files"
OmnibarItemType.SYMBOL -> "Symbols"
OmnibarItemType.AGENT -> "Agents"
OmnibarItemType.RECENT -> "Recent"
OmnibarItemType.SETTING -> "Settings"
}
}
}
}
Copy link

Copilot AI Dec 7, 2025

Choose a reason for hiding this comment

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

[nitpick] The groupByCategory function is defined but never used in the codebase. If this functionality is intended for future use, consider adding a TODO comment. Otherwise, consider removing it to reduce code maintenance burden, or integrate it into the UI if categorized display was intended.

Copilot uses AI. Check for mistakes.
AutoDevLogger.info("AutoDevMain") { "📁 Log files location: ${AutoDevLogger.getLogDirectory()}" }

val mode = args.find { it.startsWith("--mode=") }?.substringAfter("--mode=") ?: "auto"
// 检查是否跳过启动动画(通过命令行参数)
Copy link

Copilot AI Dec 7, 2025

Choose a reason for hiding this comment

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

Comments should be in English for consistency with the rest of the codebase.

Suggested translation: "Check if splash screen should be skipped (via command line argument)"

Suggested change
// 检查是否跳过启动动画(通过命令行参数)
// Check if splash screen should be skipped (via command line argument)

Copilot uses AI. Check for mistakes.
Integrate ComposeOmnibarDataProvider and enable Cmd/Ctrl+K shortcut to open Omnibar in Compose desktop app. Adds built-in commands and settings actions.
- Add ComposeOmnibarDataProvider for mpp-ui with built-in commands and settings
- Integrate Omnibar in AutoDevApp.kt with Cmd+K/Ctrl+K keyboard shortcut
- Create Omnibar.tsx and Omnibar.css for VSCode webview
- Add keyboard shortcut (Cmd+K/Ctrl+K) to open Omnibar in VSCode
- Update useVSCode.ts with Omnibar message types
Refine and expand agent-related information in AGENTS.md.
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.

[Component] 重构命令面板为全知命令栏 Omnibar

1 participant