Skip to content

Conversation

@yujonglee
Copy link
Contributor

@yujonglee yujonglee commented Nov 27, 2025

feat(extensions): implement Pattern A architecture with panels and lifecycle hooks

Summary

This PR implements the foundation for a VS Code-style extension system (Pattern A) where extensions define UI panels that render as tabs. Key changes:

  • Manifest schema update: Replaced ui: Option<String> with panels: Vec<PanelDeclaration> and added api_version field for future compatibility checking
  • Structured logging: Added op_hypr_log_error and op_hypr_log_warn ops, exposed as hypr.log.info/error/warn() with backwards compatibility for hypr.log(msg)
  • Lifecycle hooks: Extensions can now export activate(context) which is called on load with extension metadata (id, path, manifest info)
  • Tauri plugin update: ExtensionInfo now includes api_version and panels: Vec<PanelInfo> instead of ui_path
  • Hello-world example: Updated to demonstrate the new Pattern A structure with activate lifecycle

Tab System Integration

  • Added loadExtensionPanels(), getPanelInfo(), getPanelInfoByExtensionId(), getLoadedPanels(), and registerExtensionComponent() functions in registry.ts
  • TabContentExtension now shows panel info when a panel is registered but UI not loaded
  • Added build.mjs with esbuild to bundle extension panel UIs (run pnpm -F @hypr/extensions build)
  • Regenerated TypeScript bindings to include new PanelInfo type

Updates since last revision

Addressed review comments:

  • Wired extensions build into turbo.json and pnpm-workspace.yaml so dist/ui.js is produced during normal build
  • Fixed property mismatch in index.tsx (condition now checks panelInfo?.entry to match displayed value)
  • Added loadExtensionPanels() call on app startup in Body component with useEffect
  • Added getPanelInfoByExtensionId() to fix panel lookup (tabs use extension ID, not panel ID)
  • Added guard against duplicate initialization in loadExtensionPanels()
  • Added error handling for manifest read/parse in build.mjs with contextual error messages
  • Added error handling for esbuild.build() with proper exit codes on failure
  • Added top-level await for async build functions in build.mjs
  • Moved hello-world extension button from devtool to profile section - the devtool runs in a separate window with its own Zustand store instance, so calling openNew there didn't affect the main window's tabs

Review & Testing Checklist for Human

  • Breaking change: The manifest format changed from "ui": "ui.tsx" to "panels": [...]. Verify no production extensions exist using the old format
  • Dynamic import not implemented: registerExtensionComponent() exists but nothing actually loads the bundled JS and registers it - end-to-end UI rendering is incomplete
  • External dependencies in build: The build marks react, react-dom, @hypr/ui, @hypr/utils as external - verify these are available at runtime when extension UI loads
  • deactivate() not called: The runtime calls activate() but never calls deactivate() - incomplete lifecycle support

Suggested test plan:

  1. Load the hello-world extension and verify activate() is called (check logs for "Activating Hello World")
  2. Run pnpm -F @hypr/extensions build:hello-world and verify dist/ui.js is created
  3. Call list_extensions and verify the new panels array structure with entry_path populated
  4. Click "Hello World" in the profile menu (main sidebar) and verify the extension tab opens with the "panel registered but UI not loaded" message

Notes

This is foundational work for the Pattern A extension architecture. The infrastructure is in place but the end-to-end flow is not complete. Follow-up work needed:

  • Implement dynamic import to load bundled UI and call registerExtensionComponent()
  • Extension panel SDK (@hypr/extension-panel-sdk) for UI-to-host communication
  • Message bridge between UI and Deno host

Link to Devin run: https://app.devin.ai/sessions/167d232223d84b18b68c889e733412f8
Requested by: yujonglee (yujonglee.dev@gmail.com) / @yujonglee

…fecycle hooks

- Update manifest schema with api_version, panels array, and PanelDeclaration
- Add logging ops (op_hypr_log_error, op_hypr_log_warn) for structured logging
- Implement activate() lifecycle hook called on extension load with context
- Update hypr global API with structured logging (hypr.log.info/error/warn)
- Update Tauri plugin to expose PanelInfo with entry paths
- Update hello-world extension to demonstrate Pattern A with activate lifecycle

Co-Authored-By: yujonglee <yujonglee.dev@gmail.com>
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 27, 2025

📝 Walkthrough

Walkthrough

This PR refactors the extension system to introduce API versioning and a panel-based UI architecture, replacing the single ui field with multi-panel support. It adds new logging operations (error, warn), implements extension lifecycle hooks (activate/deactivate) with context passing, extends the plugin layer with panel metadata APIs, updates the frontend panel registry with caching and loading logic, and introduces build infrastructure for extension panels.

Changes

Cohort / File(s) Summary
Core Extension Runtime
crates/extensions-runtime/src/manifest.rs, crates/extensions-runtime/src/ops.rs, crates/extensions-runtime/src/runtime.rs
Added CURRENT_API_VERSION constant and api_version field to ExtensionManifest. Replaced ui: Option<String> with panels: Vec<PanelDeclaration> struct. Implemented panel_path() method replacing ui_path() with path validation. Added op_hypr_log_error and op_hypr_log_warn ops. Extended runtime to expose hypr.log as object with info, error, warn methods. Introduced __hypr_context with extension metadata passed to activation hook.
Hello World Example
extensions/hello-world/extension.json, extensions/hello-world/main.js
Added api_version field to manifest. Replaced ui field with panels array containing panel entry. Updated description text. Added __hypr_extension.activate(context) and __hypr_extension.deactivate() lifecycle hooks. Changed logging calls to use explicit hypr.log.info().
Plugin Extensions Layer
plugins/extensions/src/lib.rs, plugins/extensions/src/commands.rs
Introduced new PanelInfo struct with id, title, entry, entry_path fields. Added api_version: String and panels: Vec<PanelInfo> to ExtensionInfo. Removed ui_path field. Updated list_extensions and get_extension to populate panel data and API version.
Frontend Panel Registry & UI
apps/desktop/src/components/main/body/extensions/registry.ts, apps/desktop/src/components/main/body/extensions/index.tsx, apps/desktop/src/components/main/body/index.tsx, apps/desktop/src/components/main/sidebar/profile/index.tsx
Added in-memory panel cache with loadedPanels and extensionPanels maps. Introduced getPanelInfo(), getPanelInfoByExtensionId(), loadExtensionPanels(), getLoadedPanels(), registerExtensionComponent() public functions. Updated TabContentExtension to use panel info for conditional error messaging. Added useEffect to load panels on component mount. Added "Hello World" menu item in sidebar.
Extension Build Infrastructure
extensions/build.mjs, extensions/package.json, extensions/.gitignore
Created build script supporting per-panel esbuild bundling with React/JSX support and external dependencies. Added build and build:hello-world npm scripts. Added esbuild ^0.24.0 as devDependency. Added dist/ and node_modules/ to gitignore.
Configuration & Workspace
pnpm-workspace.yaml, turbo.json, apps/desktop/src-tauri/capabilities/default.json, plugins/extensions/build.rs, plugins/AGENTS.md
Added "extensions" to pnpm workspace packages. Broadened turbo build outputs to include */dist/**. Added "extensions:default" capability permission. Registered "get_extensions_dir" and "get_extension" commands in plugin build. Updated AGENTS.md with codegen instruction.

Sequence Diagram

sequenceDiagram
    actor User
    participant Desktop as Desktop App
    participant Registry as Panel Registry
    participant Tauri as Tauri Plugin
    participant Runtime as Extension Runtime
    participant Deno as Deno VM

    User->>Desktop: Load app
    Desktop->>Registry: loadExtensionPanels()
    Registry->>Tauri: list_extensions()
    Tauri->>Runtime: iterate installed extensions
    Runtime-->>Tauri: return Extension[] with panels & api_version
    Tauri-->>Registry: ExtensionInfo[] with PanelInfo[]
    Registry->>Registry: cache panels by id & extension
    Registry-->>Desktop: panels loaded

    User->>Desktop: Activate extension
    Desktop->>Registry: getPanelInfoByExtensionId(id)
    Registry-->>Desktop: PanelInfo (id, title, entry, path)
    Desktop->>Runtime: load extension module
    Runtime->>Deno: instantiate with __hypr_context
    Deno->>Deno: execute activation hook<br/>with context metadata
    Deno-->>Runtime: activate() logs context
    Runtime-->>Desktop: extension ready
    
    User->>Desktop: Interact with extension UI
    Desktop->>Deno: call __hypr_extension methods
    Deno->>Deno: use hypr.log.info/error/warn
    Deno-->>Desktop: return results
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60–75 minutes

  • Manifest & Runtime Logic: panel_path() validation and safety; __hypr_context object construction and lifetime; lifecycle hook invocation with context parameter
  • Panel Registry Caching: Map-based caching strategy; lazy loading and memoization; error handling during panel loading
  • Build Script: esbuild configuration; parameter parsing and CLI argument handling; error propagation and exit codes
  • Frontend Integration: Conditional UI messaging based on panel existence; registry initialization in component lifecycle

Possibly related PRs

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 37.04% 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
Title check ✅ Passed The title accurately summarizes the main change: implementing tab rendering from extensions, which aligns with the PR's core objective of establishing a Pattern A extension system with panel-based UI rendering.
Description check ✅ Passed The description comprehensively explains the changes across manifest schema updates, logging, lifecycle hooks, Tauri plugin updates, and tab system integration, directly corresponding to the implemented changeset.
✨ 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 devin/1764233435-extension-pattern-a

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

@netlify
Copy link

netlify bot commented Nov 27, 2025

Deploy Preview for hyprnote-storybook ready!

Name Link
🔨 Latest commit d75ea86
🔍 Latest deploy log https://app.netlify.com/projects/hyprnote-storybook/deploys/692832e8b2617300084868fd
😎 Deploy Preview https://deploy-preview-1949--hyprnote-storybook.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@devin-ai-integration
Copy link
Contributor

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR that start with 'DevinAI' or '@devin'.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment and CI monitoring

@netlify
Copy link

netlify bot commented Nov 27, 2025

Deploy Preview for hyprnote ready!

Name Link
🔨 Latest commit d75ea86
🔍 Latest deploy log https://app.netlify.com/projects/hyprnote/deploys/692832e84bd9050008ed56f1
😎 Deploy Preview https://deploy-preview-1949--hyprnote.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

Copy link
Contributor

@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: 1

🧹 Nitpick comments (3)
crates/extensions-runtime/src/manifest.rs (1)

72-88: Consider logging when panel path resolution fails.

The panel_path method silently returns None when canonicalization fails or path traversal is detected. While returning None is a safe default, the caller has no way to distinguish between "panel not found" and "panel path is invalid/escapes directory". This could make debugging extension issues harder.

Consider returning a Result or at minimum logging a warning:

     .and_then(|panel| {
         let canonical_root = self.path.canonicalize().ok()?;
         let joined = self.path.join(&panel.entry);
-        let canonical_panel = joined.canonicalize().ok()?;
+        let canonical_panel = match joined.canonicalize() {
+            Ok(p) => p,
+            Err(e) => {
+                tracing::warn!("Failed to resolve panel path for {}: {}", panel_id, e);
+                return None;
+            }
+        };

         if canonical_panel.starts_with(&canonical_root) {
             Some(canonical_panel)
         } else {
+            tracing::warn!("Panel path escapes extension directory: {}", panel_id);
             None
         }
     })
crates/extensions-runtime/src/runtime.rs (1)

233-233: Memory leak via Box::leak for script name.

Box::leak intentionally leaks the string to satisfy the 'static lifetime requirement for V8 stack traces. This is a known pattern, but each loaded extension permanently allocates memory for its ID.

If extensions are reloaded frequently or many extensions are loaded over the application lifetime, this could accumulate. Consider documenting this tradeoff or exploring alternatives if extension hot-reloading becomes a use case.

plugins/extensions/src/commands.rs (1)

53-75: Consider extracting shared panel mapping logic.

The panel mapping and ExtensionInfo construction logic is duplicated between list_extensions (lines 53-75) and get_extension (lines 114-136). This violates DRY principles and makes future updates more error-prone.

Consider extracting a helper function:

fn ext_to_info(ext: hypr_extensions_runtime::Extension) -> ExtensionInfo {
    let panels = ext
        .panels()
        .iter()
        .map(|p| PanelInfo {
            id: p.id.clone(),
            title: p.title.clone(),
            entry: p.entry.clone(),
            entry_path: ext
                .panel_path(&p.id)
                .map(|p| p.to_string_lossy().to_string()),
        })
        .collect();
    
    ExtensionInfo {
        id: ext.manifest.id.clone(),
        name: ext.manifest.name.clone(),
        version: ext.manifest.version.clone(),
        api_version: ext.manifest.api_version.clone(),
        description: ext.manifest.description.clone(),
        path: ext.path.to_string_lossy().to_string(),
        panels,
    }
}

Then simplify both functions:

 pub async fn list_extensions<R: tauri::Runtime>(
     app: tauri::AppHandle<R>,
 ) -> Result<Vec<ExtensionInfo>, Error> {
     // ... existing code ...
     
-    Ok(extensions
-        .into_iter()
-        .map(|ext| {
-            let panels = ext
-                .panels()
-                .iter()
-                .map(|p| PanelInfo { /* ... */ })
-                .collect();
-            ExtensionInfo { /* ... */ }
-        })
-        .collect())
+    Ok(extensions.into_iter().map(ext_to_info).collect())
 }
 pub async fn get_extension<R: tauri::Runtime>(
     app: tauri::AppHandle<R>,
     extension_id: String,
 ) -> Result<ExtensionInfo, Error> {
     // ... existing code ...
     
     extensions
         .into_iter()
         .find(|ext| ext.manifest.id == extension_id)
-        .map(|ext| {
-            let panels = ext
-                .panels()
-                .iter()
-                .map(|p| PanelInfo { /* ... */ })
-                .collect();
-            ExtensionInfo { /* ... */ }
-        })
+        .map(ext_to_info)
         .ok_or_else(|| Error::ExtensionNotFound(extension_id))
 }

Also applies to: 114-136

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7350799 and 80d07e6.

📒 Files selected for processing (7)
  • crates/extensions-runtime/src/manifest.rs (2 hunks)
  • crates/extensions-runtime/src/ops.rs (1 hunks)
  • crates/extensions-runtime/src/runtime.rs (4 hunks)
  • extensions/hello-world/extension.json (1 hunks)
  • extensions/hello-world/main.js (1 hunks)
  • plugins/extensions/src/commands.rs (3 hunks)
  • plugins/extensions/src/lib.rs (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
crates/extensions-runtime/src/runtime.rs (1)
crates/extensions-runtime/src/ops.rs (3)
  • op_hypr_log (5-8)
  • op_hypr_log_error (12-15)
  • op_hypr_log_warn (19-22)
plugins/extensions/src/commands.rs (1)
crates/extensions-runtime/src/manifest.rs (1)
  • panels (90-92)
⏰ 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). (7)
  • GitHub Check: Redirect rules - hyprnote
  • GitHub Check: Header rules - hyprnote
  • GitHub Check: Pages changed - hyprnote
  • GitHub Check: ci (linux, depot-ubuntu-22.04-8)
  • GitHub Check: ci (macos, depot-macos-14)
  • GitHub Check: ci (linux, depot-ubuntu-24.04-8)
  • GitHub Check: fmt
🔇 Additional comments (14)
crates/extensions-runtime/src/ops.rs (1)

9-22: LGTM!

The new op_hypr_log_error and op_hypr_log_warn ops follow the same pattern as the existing op_hypr_log and correctly use the appropriate tracing log levels.

crates/extensions-runtime/src/manifest.rs (3)

4-5: LGTM!

Good approach to introduce API versioning with a constant. This will help with backward compatibility checks in the future.


22-31: LGTM!

The PanelDeclaration struct is well-designed with appropriate derives. The default_api_version function provides a sensible default for backward compatibility.


90-92: LGTM!

Simple accessor for panels is clean.

crates/extensions-runtime/src/runtime.rs (4)

11-14: LGTM!

The extension macro correctly registers the new logging ops alongside the existing one.


134-149: Good backward compatibility approach.

The shim maintains the callable hypr.log(msg) signature while also exposing hypr.log.info/error/warn methods. This ensures existing extensions continue to work.


268-273: LGTM!

The log message now includes the API version, which aids debugging and visibility into which API version each extension uses.


216-231: Acknowledge the format injection risk, but clarify that extensions are application-bundled, not user-provided.

The format! macro does interpolate extension code directly into the JavaScript wrapper. However, this pattern is lower risk than initially suggested because extensions are loaded from the filesystem (./extensions/ directory) as part of the application, not from user input or remote sources. Extensions undergo path validation (line 57-62 in manifest.rs) to prevent directory traversal.

That said, the review comment's suggestion is still valid: deno_core 0.338 provides module evaluation APIs (load_*_es_module) that would eliminate the format string interpolation pattern entirely and provide better defense-in-depth. If the application plans to support user-provided or remotely-sourced extensions in the future, this refactoring should be prioritized.

Consider using JsRuntime::load_main_es_module_from_code() followed by mod_evaluate() instead of wrapping code in a format string and calling execute_script().

extensions/hello-world/main.js (3)

13-22: LGTM!

The functions correctly use hypr.log.info() which aligns with the new structured logging API while maintaining backward compatibility through the runtime's shim.


24-30: LGTM!

The description update reflects the Pattern A architecture change.


1-11: Reviewer's concern is confirmed: deactivate is not invoked by the runtime.

The runtime code shows that activate is called automatically during extension loading (lines 223-225 of load_extension_impl), but there is no corresponding call to deactivate. When the runtime shuts down (lines 189-191), it simply breaks the event loop without notifying any loaded extensions. There is also no mechanism to unload individual extensions. The extension lifecycle is incomplete.

plugins/extensions/src/lib.rs (2)

26-32: LGTM on PanelInfo structure.

The PanelInfo struct appropriately models panel metadata with id, title, entry, and an optional entry_path. The optional entry_path correctly handles cases where the path resolution might fail.


20-24: Add API version validation during extension loading to prevent incompatible versions from silently failing at runtime.

The api_version field has implicit backwards compatibility via serde default (defaulting to "0.1" for old manifests without the field), so migration is handled. However, no validation exists: extensions specifying unsupported or future api_version values load successfully but may fail during runtime execution.

Consider adding a check in Extension::load() (crates/extensions-runtime/src/manifest.rs) to validate that api_version is supported, e.g., comparing against CURRENT_API_VERSION and returning an error if incompatible.

plugins/extensions/src/commands.rs (1)

5-5: LGTM on import addition.

The PanelInfo import is correctly added to support the new panel-based architecture.

devin-ai-integration bot and others added 2 commits November 27, 2025 09:22
…xtension panels

Co-Authored-By: yujonglee <yujonglee.dev@gmail.com>
Co-Authored-By: yujonglee <yujonglee.dev@gmail.com>
Copy link
Contributor

@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 (3)
apps/desktop/src/components/main/body/extensions/registry.ts (1)

77-82: Consider adding guards and feedback to component registration.

The registerExtensionComponent function silently overwrites existing components without warning, which could make debugging difficult if an extension is accidentally registered twice.

Consider adding validation and logging:

 export function registerExtensionComponent(
   extensionId: string,
   component: ComponentType<ExtensionViewProps>,
 ): void {
+  if (dynamicExtensionComponents[extensionId]) {
+    console.warn(`Extension component "${extensionId}" is being overwritten`);
+  }
   dynamicExtensionComponents[extensionId] = component;
+  console.log(`Registered extension component: ${extensionId}`);
 }
extensions/build.mjs (2)

18-20: Refine entry path transformation logic for robustness.

The string replacement approach (replace("dist/", "").replace(".js", ".tsx")) is fragile:

  • Replaces all occurrences, not just prefix/suffix
  • Doesn't handle edge cases like missing "dist/", ".mjs` files, or paths like "test.js.backup"
  • Assumes a specific directory structure

Consider using path manipulation utilities for more robust transformation:

-    const entryFile = panel.entry.replace("dist/", "").replace(".js", ".tsx");
+    // Remove dist/ prefix if present, change extension to .tsx
+    let entryFile = panel.entry;
+    if (entryFile.startsWith("dist/")) {
+      entryFile = entryFile.slice(5); // Remove "dist/"
+    }
+    // Replace .js or .mjs extension with .tsx
+    entryFile = entryFile.replace(/\.(m?js)$/, ".tsx");
     const entryPath = path.join(extensionDir, entryFile);

Or more explicitly:

-    const entryFile = panel.entry.replace("dist/", "").replace(".js", ".tsx");
+    const parsed = path.parse(panel.entry);
+    const dirWithoutDist = parsed.dir.replace(/^dist\/?/, "");
+    const entryFile = path.join(dirWithoutDist, parsed.name + ".tsx");
     const entryPath = path.join(extensionDir, entryFile);

22-25: Consider adding a strict mode for builds.

The script currently warns and continues when a panel entry file is missing. While this is reasonable for development, you might want a strict mode that fails the build in CI/production.

Consider adding an environment variable check:

     if (!fs.existsSync(entryPath)) {
       console.warn(`Panel entry not found: ${entryPath}, skipping...`);
+      if (process.env.STRICT_BUILD === "true") {
+        throw new Error(`Missing required panel entry: ${entryPath}`);
+      }
       continue;
     }
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 80d07e6 and f0d33b4.

⛔ Files ignored due to path filters (1)
  • plugins/extensions/js/bindings.gen.ts is excluded by !**/*.gen.ts
📒 Files selected for processing (5)
  • apps/desktop/src/components/main/body/extensions/index.tsx (3 hunks)
  • apps/desktop/src/components/main/body/extensions/registry.ts (3 hunks)
  • extensions/.gitignore (1 hunks)
  • extensions/build.mjs (1 hunks)
  • extensions/package.json (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • extensions/.gitignore
🧰 Additional context used
📓 Path-based instructions (2)
**/*.ts

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.ts: Agent implementations should use TypeScript and follow the established architectural patterns defined in the agent framework
Agent communication should use defined message protocols and interfaces

Files:

  • apps/desktop/src/components/main/body/extensions/registry.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: Avoid creating a bunch of types/interfaces if they are not shared. Especially for function props, just inline them instead.
Never do manual state management for form/mutation. Use useForm (from tanstack-form) and useQuery/useMutation (from tanstack-query) instead for 99% of cases. Avoid patterns like setError.
If there are many classNames with conditional logic, use cn (import from @hypr/utils). It is similar to clsx. Always pass an array and split by logical grouping.
Use motion/react instead of framer-motion.

Files:

  • apps/desktop/src/components/main/body/extensions/registry.ts
  • apps/desktop/src/components/main/body/extensions/index.tsx
🧬 Code graph analysis (2)
apps/desktop/src/components/main/body/extensions/registry.ts (3)
packages/tiptap/src/shared/extensions/index.ts (1)
  • extensions (188-188)
apps/desktop/src/types/extensions.d.ts (1)
  • ExtensionViewProps (3-6)
extensions/hello-world/ui.tsx (1)
  • ExtensionViewProps (13-16)
apps/desktop/src/components/main/body/extensions/index.tsx (1)
apps/desktop/src/components/main/body/extensions/registry.ts (1)
  • getPanelInfo (59-61)
⏰ 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). (7)
  • GitHub Check: Redirect rules - hyprnote
  • GitHub Check: Header rules - hyprnote
  • GitHub Check: Pages changed - hyprnote
  • GitHub Check: ci (linux, depot-ubuntu-24.04-8)
  • GitHub Check: ci (linux, depot-ubuntu-22.04-8)
  • GitHub Check: ci (macos, depot-macos-14)
  • GitHub Check: fmt
🔇 Additional comments (1)
apps/desktop/src/components/main/body/extensions/index.tsx (1)

92-92: I'll help you verify the review comment about the ID mapping issue between tab.extensionId and panel ID. Let me start by exploring the codebase to understand the structure.
<function_calls>

Search for getPanelInfo definition, tab structure, and panel registry

#!/bin/bash

echo "=== Looking for getPanelInfo definition ==="
rg -n "getPanelInfo" --type ts --type tsx -B2 -A5

echo -e "\n=== Looking for panel registry and loadedPanels ==="
rg -n "loadedPanels" --type ts --type tsx -B2 -A2

echo -e "\n=== Looking for tab type definition ==="
rg -n "type.*Tab|interface.*Tab" --type ts --type tsx -B2 -A5

<function_calls>

Get the context around line 92 in the target file to understand the usage

#!/bin/bash

First verify the file exists and check its size

if [ -f "apps/desktop/src/components/main/body/extensions/index.tsx" ]; then
wc -l "apps/desktop/src/components/main/body/extensions/index.tsx"
echo "=== Context around line 92 ==="
sed -n '80,120p' "apps/desktop/src/components/main/body/extensions/index.tsx"
else
echo "File not found, searching for it..."
fd -e tsx -e ts "index.tsx" | grep extensions
fi


</function_calls>

Copy link
Contributor

@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 (4)
apps/desktop/src/components/main/body/extensions/registry.ts (1)

63-86: Ensure loadExtensionPanels() is invoked so panel helpers ever return data.

The helper functions (getPanelInfo, getLoadedPanels, registerExtensionComponent) look good, but without calling loadExtensionPanels() during app startup, loadedPanels stays empty and these APIs effectively become no-ops. A previous review already flagged the missing call sites; please re-check after your latest changes.

To verify current usage, you can run:

#!/bin/bash
# Search for any callers of loadExtensionPanels
rg -n "loadExtensionPanels" --type ts --type tsx -C3
extensions/build.mjs (3)

74-78: Use top‑level await (or an async main()) so build completion and failures are explicit.

Calling the async functions without awaiting works but makes success/failure handling depend on unhandled‑rejection behavior; awaiting here clarifies control flow and exit codes.

-if (extensionName) {
-  buildExtension(extensionName);
-} else {
-  buildAll();
-}
+if (extensionName) {
+  await buildExtension(extensionName);
+} else {
+  await buildAll();
+}

(If your Node/tooling doesn’t like top‑level await, you can instead wrap this in an async function main() { ... } main().catch(err => { console.error(err); process.exit(1); });.)


11-16: Guard manifest read/parse with try/catch so one bad extension doesn’t kill the build.

JSON.parse(fs.readFileSync(...)) will currently throw on IO or syntax errors and abort the whole script; better to log and skip that extension instead.

-  const manifest = JSON.parse(fs.readFileSync(manifestPath, "utf-8"));
+  let manifest;
+  try {
+    manifest = JSON.parse(fs.readFileSync(manifestPath, "utf-8"));
+  } catch (error) {
+    console.error(
+      `Failed to read or parse manifest for ${name} at ${manifestPath}:`,
+      error,
+    );
+    return;
+  }

Based on learnings, manifest error handling should cover common failure scenarios rather than hard-crashing the build.


34-47: Wrap esbuild.build in try/catch and fail with a clear, contextual error.

Right now a bundling failure surfaces as a generic unhandled rejection; you lose which extension/panel failed and may not get a clean non‑zero exit code.

-    console.log(`Building ${name}/${panel.id}: ${entryFile} -> ${panel.entry}`);
-
-    await esbuild.build({
-      entryPoints: [entryPath],
-      bundle: true,
-      outfile,
-      format: "esm",
-      platform: "browser",
-      target: "es2020",
-      jsx: "automatic",
-      external: ["react", "react-dom", "@hypr/ui", "@hypr/utils"],
-      minify: false,
-      sourcemap: true,
-    });
+    console.log(`Building ${name}/${panel.id}: ${entryFile} -> ${panel.entry}`);
+
+    try {
+      await esbuild.build({
+        entryPoints: [entryPath],
+        bundle: true,
+        outfile,
+        format: "esm",
+        platform: "browser",
+        target: "es2020",
+        jsx: "automatic",
+        external: ["react", "react-dom", "@hypr/ui", "@hypr/utils"],
+        minify: false,
+        sourcemap: true,
+      });
+    } catch (error) {
+      console.error(
+        `Failed to build panel ${name}/${panel.id}: ${entryPath} -> ${panel.entry}`,
+        error,
+      );
+      process.exit(1);
+    }

Please double‑check this against your esbuild version’s error semantics and recommended CLI patterns.

🧹 Nitpick comments (1)
apps/desktop/src/components/main/body/extensions/registry.ts (1)

21-21: Consider Map lifecycle if extensions can be reloaded/uninstalled.

loadedPanels only ever grows; if loadExtensionPanels() is called again after extensions change, stale entries will remain in the cache. If re-initialization is expected, clear the map first:

export async function loadExtensionPanels(): Promise<void> {
+  loadedPanels.clear();
+
   const extensions = await listInstalledExtensions();
   for (const ext of extensions) {
     for (const panel of ext.panels) {
       loadedPanels.set(panel.id, panel);
     }
   }
}
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f0d33b4 and 04c651e.

📒 Files selected for processing (2)
  • apps/desktop/src/components/main/body/extensions/registry.ts (3 hunks)
  • extensions/build.mjs (1 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.ts

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.ts: Agent implementations should use TypeScript and follow the established architectural patterns defined in the agent framework
Agent communication should use defined message protocols and interfaces

Files:

  • apps/desktop/src/components/main/body/extensions/registry.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: Avoid creating a bunch of types/interfaces if they are not shared. Especially for function props, just inline them instead.
Never do manual state management for form/mutation. Use useForm (from tanstack-form) and useQuery/useMutation (from tanstack-query) instead for 99% of cases. Avoid patterns like setError.
If there are many classNames with conditional logic, use cn (import from @hypr/utils). It is similar to clsx. Always pass an array and split by logical grouping.
Use motion/react instead of framer-motion.

Files:

  • apps/desktop/src/components/main/body/extensions/registry.ts
🧠 Learnings (1)
📚 Learning: 2025-11-24T16:32:13.593Z
Learnt from: CR
Repo: fastrepl/hyprnote PR: 0
File: packages/nango/.cursor/rules/nango.mdc:0-0
Timestamp: 2025-11-24T16:32:13.593Z
Learning: Applies to packages/nango/**/*.ts : Error handling must cover common failure scenarios

Applied to files:

  • extensions/build.mjs
⏰ 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: ci (linux, depot-ubuntu-24.04-8)
  • GitHub Check: ci (linux, depot-ubuntu-22.04-8)
  • GitHub Check: ci (macos, depot-macos-14)
🔇 Additional comments (2)
apps/desktop/src/components/main/body/extensions/registry.ts (1)

3-7: Imports updated for panel metadata look correct.

Using PanelInfo from @hypr/plugin-extensions keeps this registry aligned with the backend API; no issues here.

extensions/build.mjs (1)

53-72: buildAll() directory scan and sequencing look solid.

Filtering dot directories/node_modules and building only when extension.json exists is reasonable, and sequential await buildExtension(entry.name) keeps the pipeline simple and predictable.

@yujonglee yujonglee changed the title feat(extensions): implement Pattern A architecture with panels and lifecycle hooks feat(extensions): implement tab rendering from extension Nov 27, 2025
devin-ai-integration bot and others added 2 commits November 27, 2025 09:53
- Wire extensions build into turbo.json and pnpm-workspace.yaml
- Fix property mismatch in index.tsx (check panelInfo?.entry instead of entry_path)
- Call loadExtensionPanels() on app startup in Body component
- Add getPanelInfoByExtensionId() to fix panel lookup by extension ID
- Add guard against duplicate initialization in loadExtensionPanels()
- Add error handling for fs.readFileSync/JSON.parse in build.mjs
- Add error handling for esbuild.build call in build.mjs
- Add top-level await for buildExtension() and buildAll() calls

Co-Authored-By: yujonglee <yujonglee.dev@gmail.com>
Co-Authored-By: yujonglee <yujonglee.dev@gmail.com>
Copy link
Contributor

@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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/desktop/src/components/main/body/index.tsx (1)

28-44: Trigger a re‑render after panels finish loading to avoid stale panel info

The startup useEffect correctly initializes loadExtensionPanels(), but because that function only mutates module‑level Maps, components like TabContentExtension won’t re‑read panelInfo when the async load completes. For extension tabs that are already open on first render (e.g., from persisted tab state) this can leave them stuck showing “Extension not found”.

One lightweight fix is to drive a dummy piece of state off the load:

 import { useCallback, useEffect, useRef } from "react";
+import { useState } from "react";
@@
 export function Body() {
+  const [, setPanelsLoadedVersion] = useState(0);
@@
-  useEffect(() => {
-    loadExtensionPanels();
-  }, []);
+  useEffect(() => {
+    void (async () => {
+      await loadExtensionPanels();
+      // Bump a version so descendants (including TabContentExtension)
+      // re-read panel info after panels are loaded.
+      setPanelsLoadedVersion((v) => v + 1);
+    })();
+  }, []);

You don’t need to use the panelsLoadedVersion value anywhere; just updating it once ensures any already‑rendered extension tabs will re-render and see the populated panel registry.

🧹 Nitpick comments (2)
extensions/build.mjs (1)

16-23: Consider propagating manifest read/parse failures via a non‑zero exit code

Right now, if extension.json is missing or invalid, buildExtension() logs and returns but the script still exits with code 0, even when invoked for a single extension. That makes CI/dev scripts think the build succeeded when it silently skipped that extension.

A minimal pattern is to have buildExtension/buildAll return a success flag and use it at the top level:

-async function buildExtension(name) {
+async function buildExtension(name): Promise<boolean> {
   const extensionDir = path.join(process.cwd(), name);
   const manifestPath = path.join(extensionDir, "extension.json");

   if (!fs.existsSync(manifestPath)) {
     console.error(`Extension manifest not found: ${manifestPath}`);
-    return;
+    return false;
   }

   let manifest;
   try {
     const raw = fs.readFileSync(manifestPath, "utf-8");
     manifest = JSON.parse(raw);
   } catch (err) {
     console.error(`Failed to read/parse manifest ${manifestPath}:`, err);
-    return;
+    return false;
   }

   for (const panel of manifest.panels || []) {
     // ...
   }

   console.log(`Built extension: ${name}`);
-}
+  return true;
+}

-async function buildAll() {
+async function buildAll(): Promise<boolean> {
   const entries = fs.readdirSync(process.cwd(), { withFileTypes: true });
+  let hadFailures = false;

   for (const entry of entries) {
     // ...
       if (fs.existsSync(manifestPath)) {
-        await buildExtension(entry.name);
+        const ok = await buildExtension(entry.name);
+        if (!ok) {
+          hadFailures = true;
+        }
       }
     }
   }
+
+  return !hadFailures;
 }

-if (extensionName) {
-  await buildExtension(extensionName);
-} else {
-  await buildAll();
-}
+if (extensionName) {
+  const ok = await buildExtension(extensionName);
+  process.exit(ok ? 0 : 1);
+} else {
+  const ok = await buildAll();
+  process.exit(ok ? 0 : 1);
+}

This keeps individual manifest failures from aborting other builds, but still makes the overall script fail visibly when anything went wrong.

Also applies to: 99-103

apps/desktop/src/components/main/body/extensions/registry.ts (1)

21-23: Panel registry design is solid; consider future multi‑panel and refresh needs

The in‑memory registry (loadedPanels, extensionPanels, panelsLoaded) plus:

  • getPanelInfo(panelId)
  • getPanelInfoByExtensionId(extensionId) (first panel)
  • loadExtensionPanels() with an idempotent guard
  • getLoadedPanels()

gives a clean, minimal API for the current “one primary panel per extension” model.

Two things to keep in mind as this evolves (no change required now):

  1. Multiple panels per extension
    getPanelInfoByExtensionId returning panels?.[0] is fine today, but once tabs become panel‑aware you’ll likely want either a (extensionId, panelId) lookup or a getPanelsByExtensionId helper to avoid overloading “first panel” semantics.

  2. Runtime extension changes / refresh
    With panelsLoaded as a simple boolean, newly installed or removed extensions during a session won’t be visible until restart. If hot‑reloading extensions becomes a goal, a future reloadExtensionPanels() that clears the Maps and resets panelsLoaded (or accepts an invalidate flag) would make this component more flexible.

Overall, the structure fits the current requirements and is a good base to grow from.

Also applies to: 65-101

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 04c651e and 9ec3361.

📒 Files selected for processing (6)
  • apps/desktop/src/components/main/body/extensions/index.tsx (3 hunks)
  • apps/desktop/src/components/main/body/extensions/registry.ts (3 hunks)
  • apps/desktop/src/components/main/body/index.tsx (2 hunks)
  • extensions/build.mjs (1 hunks)
  • pnpm-workspace.yaml (1 hunks)
  • turbo.json (1 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: Avoid creating a bunch of types/interfaces if they are not shared. Especially for function props, just inline them instead.
Never do manual state management for form/mutation. Use useForm (from tanstack-form) and useQuery/useMutation (from tanstack-query) instead for 99% of cases. Avoid patterns like setError.
If there are many classNames with conditional logic, use cn (import from @hypr/utils). It is similar to clsx. Always pass an array and split by logical grouping.
Use motion/react instead of framer-motion.

Files:

  • apps/desktop/src/components/main/body/index.tsx
  • apps/desktop/src/components/main/body/extensions/registry.ts
  • apps/desktop/src/components/main/body/extensions/index.tsx
**/*.ts

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.ts: Agent implementations should use TypeScript and follow the established architectural patterns defined in the agent framework
Agent communication should use defined message protocols and interfaces

Files:

  • apps/desktop/src/components/main/body/extensions/registry.ts
🧠 Learnings (1)
📚 Learning: 2025-11-24T16:32:13.593Z
Learnt from: CR
Repo: fastrepl/hyprnote PR: 0
File: packages/nango/.cursor/rules/nango.mdc:0-0
Timestamp: 2025-11-24T16:32:13.593Z
Learning: Applies to packages/nango/**/*.ts : Error handling must cover common failure scenarios

Applied to files:

  • extensions/build.mjs
🧬 Code graph analysis (3)
apps/desktop/src/components/main/body/index.tsx (1)
apps/desktop/src/components/main/body/extensions/registry.ts (1)
  • loadExtensionPanels (76-97)
apps/desktop/src/components/main/body/extensions/registry.ts (4)
crates/extensions-runtime/src/manifest.rs (1)
  • panels (90-92)
packages/tiptap/src/shared/extensions/index.ts (1)
  • extensions (188-188)
apps/desktop/src/types/extensions.d.ts (1)
  • ExtensionViewProps (3-6)
extensions/hello-world/ui.tsx (1)
  • ExtensionViewProps (13-16)
apps/desktop/src/components/main/body/extensions/index.tsx (1)
apps/desktop/src/components/main/body/extensions/registry.ts (1)
  • getPanelInfoByExtensionId (69-74)
🔇 Additional comments (3)
pnpm-workspace.yaml (1)

5-5: The review comment is incorrect and should be resolved.

The workspace configuration is correct as written. While the extensions/ directory contains a subdirectory (hello-world/), it does not have its own package.json file. The hello-world directory is a subdirectory within the extensions package itself, not a separate workspace package. Therefore, using the exact path "extensions" is the correct configuration—matching the single workspace package at that location. The glob pattern "extensions/*" would only be appropriate if subdirectories contained their own package.json files (as they do under apps/, packages/, and plugins/).

turbo.json (1)

14-14: Build outputs glob change looks good

Tracking */dist/** alongside storybook-static/** is consistent with the new per‑package build artifacts and keeps Turbo’s caching behavior aligned with the actual outputs.

apps/desktop/src/components/main/body/extensions/index.tsx (1)

17-18: TabContentExtension messaging and panelInfo usage look consistent

Using getPanelInfoByExtensionId(tab.extensionId) to distinguish between:

  • a known panel without a registered UI component, and
  • a missing extension ID,

and then showing panelInfo.entry when available, matches the registry’s current shape and provides clearer debugging info.

Once the Body useEffect triggers a re-render after loadExtensionPanels() completes (see comment in apps/desktop/src/components/main/body/index.tsx), this UI will correctly update for tabs hydrated on startup as well.

Also applies to: 90-108

@devin-ai-integration
Copy link
Contributor

Devin is archived and cannot be woken up. Please unarchive Devin if you want to continue using it.

Copy link
Contributor

@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

🧹 Nitpick comments (1)
apps/desktop/src/routes/app/devtool.tsx (1)

17-17: Extensions devtool wiring looks good; consider de-hardcoding the target extension.

The useTabs integration and openNew({ type: "extension", extensionId: "hello-world" }) call are consistent with the tab store usage and are fine as a smoke test. Longer term, you may want to either (a) derive the extension(s) listed here from the extension/Panel registry, or (b) at least centralize "hello-world" as a shared constant so the devtool doesn’t drift if the manifest id changes.

Also applies to: 106-106, 198-218

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9ec3361 and dbf9862.

⛔ Files ignored due to path filters (6)
  • plugins/extensions/permissions/autogenerated/commands/get_extension.toml is excluded by !plugins/**/permissions/**
  • plugins/extensions/permissions/autogenerated/commands/get_extensions_dir.toml is excluded by !plugins/**/permissions/**
  • plugins/extensions/permissions/autogenerated/reference.md is excluded by !plugins/**/permissions/**
  • plugins/extensions/permissions/default.toml is excluded by !plugins/**/permissions/**
  • plugins/extensions/permissions/schemas/schema.json is excluded by !plugins/**/permissions/**
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (4)
  • apps/desktop/src-tauri/capabilities/default.json (1 hunks)
  • apps/desktop/src/routes/app/devtool.tsx (3 hunks)
  • plugins/AGENTS.md (1 hunks)
  • plugins/extensions/build.rs (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • plugins/AGENTS.md
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: Avoid creating a bunch of types/interfaces if they are not shared. Especially for function props, just inline them instead.
Never do manual state management for form/mutation. Use useForm (from tanstack-form) and useQuery/useMutation (from tanstack-query) instead for 99% of cases. Avoid patterns like setError.
If there are many classNames with conditional logic, use cn (import from @hypr/utils). It is similar to clsx. Always pass an array and split by logical grouping.
Use motion/react instead of framer-motion.

Files:

  • apps/desktop/src/routes/app/devtool.tsx
🧬 Code graph analysis (1)
apps/desktop/src/routes/app/devtool.tsx (1)
apps/desktop/src/store/zustand/tabs/index.ts (1)
  • useTabs (29-38)
⏰ 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). (7)
  • GitHub Check: Redirect rules - hyprnote
  • GitHub Check: Header rules - hyprnote
  • GitHub Check: Pages changed - hyprnote
  • GitHub Check: fmt
  • GitHub Check: ci (linux, depot-ubuntu-24.04-8)
  • GitHub Check: ci (linux, depot-ubuntu-22.04-8)
  • GitHub Check: ci (macos, depot-macos-14)
🔇 Additional comments (3)
apps/desktop/src-tauri/capabilities/default.json (2)

72-72: The review comment is based on incorrect assumptions about the repository structure.

Verification shows that only one capability definition file exists in the repository: apps/desktop/src-tauri/capabilities/default.json. There are no role-specific, feature-gated, or security-sensitive capability sets to review. The extensions:default permission at line 72 is appropriately placed in the sole capability file, and no other files require this permission.


72-72: extensions:default capability is properly integrated; no action needed.

The capability is correctly added to the permission array in apps/desktop/src-tauri/capabilities/default.json and follows Tauri's plugin capability naming convention (<plugin_name>:default). The extensions plugin is fully implemented at plugins/extensions/ with all commands defined, and the capability file is the only location where plugin capabilities are declared in this project. The schema is auto-generated from these capability definitions, so no additional configuration is required.

plugins/extensions/build.rs (1)

6-7: Commands are properly implemented and integrated; no frontend consumption yet (expected for new API).

Both get_extensions_dir and get_extension commands are fully implemented in commands.rs with complete error handling and logic, correctly registered in lib.rs, and properly declared in build.rs. The implementations follow the Tauri plugin pattern and integrate with the extension discovery system.

Frontend usage was not detected, but this is expected since these are newly added APIs. The backend infrastructure is complete and ready for consumption.

…ofile section

Co-Authored-By: yujonglee <yujonglee.dev@gmail.com>
Copy link
Contributor

@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

🧹 Nitpick comments (1)
apps/desktop/src/components/main/sidebar/profile/index.tsx (1)

162-173: Extension menu wiring looks good; consider avoiding hard‑coded demo values

handleClickExtension correctly mirrors the existing patterns (invokes openNew({ type: "extension", extensionId: "hello-world" }) then closeMenu), and the "Hello World" menu item is properly hooked up with PuzzleIcon.

For long‑term UX, consider:

  • Driving the label/icon from extension metadata instead of hard‑coding.
  • Hiding/guarding this item if the hello-world extension (or a default panel for it) is not installed/loaded, so users don’t hit a dead tab in production.
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between dbf9862 and d75ea86.

📒 Files selected for processing (1)
  • apps/desktop/src/components/main/sidebar/profile/index.tsx (2 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: Avoid creating a bunch of types/interfaces if they are not shared. Especially for function props, just inline them instead.
Never do manual state management for form/mutation. Use useForm (from tanstack-form) and useQuery/useMutation (from tanstack-query) instead for 99% of cases. Avoid patterns like setError.
If there are many classNames with conditional logic, use cn (import from @hypr/utils). It is similar to clsx. Always pass an array and split by logical grouping.
Use motion/react instead of framer-motion.

Files:

  • apps/desktop/src/components/main/sidebar/profile/index.tsx
⏰ 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). (7)
  • GitHub Check: Redirect rules - hyprnote
  • GitHub Check: Header rules - hyprnote
  • GitHub Check: ci (linux, depot-ubuntu-22.04-8)
  • GitHub Check: ci (macos, depot-macos-14)
  • GitHub Check: ci (linux, depot-ubuntu-24.04-8)
  • GitHub Check: fmt
  • GitHub Check: Pages changed - hyprnote
🔇 Additional comments (1)
apps/desktop/src/components/main/sidebar/profile/index.tsx (1)

1-10: New PuzzleIcon import is correctly wired

PuzzleIcon is imported from lucide-react and used in menuItems, so there’s no unused-import or typing concern here.

@yujonglee yujonglee merged commit d53f5f5 into main Nov 27, 2025
15 checks passed
@yujonglee yujonglee deleted the devin/1764233435-extension-pattern-a branch November 27, 2025 11:39
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.

2 participants