Skip to content

feat: port memory system + custom agents to desktop architecture#1955

Open
vitorafgomes wants to merge 3 commits intoAndyMik90:developfrom
vitorafgomes:feature/port-memory-agents-to-desktop
Open

feat: port memory system + custom agents to desktop architecture#1955
vitorafgomes wants to merge 3 commits intoAndyMik90:developfrom
vitorafgomes:feature/port-memory-agents-to-desktop

Conversation

@vitorafgomes
Copy link

@vitorafgomes vitorafgomes commented Mar 14, 2026

Summary

Ports the viable frontend changes from PR #1920 to the current apps/desktop/ structure, after upstream restructured apps/frontend/apps/desktop/ and migrated the backend from Python to TypeScript (Vercel AI SDK v6).

What's included

  • Custom Agents integration: IPC handler reads ~/.claude/agents/ definitions, specialist agents catalog in AgentProfileSettings
  • Global MCP support: IPC handler for Claude Code MCP config, health checks, pipeline phase assignment UI in AgentTools
  • Customer Projects: AddCustomerModal, CustomerReposModal, multi-repo GitHub hooks (issues + PRs), RepoFilterDropdown, Sidebar integration
  • Memory improvements: Ollama embedding dimension lookup (30+ models), index progress tracking in context store
  • i18n: New context namespace, 100+ translation keys (en + fr) for settings, dialogs, navigation, tasks
  • Type safety fixes: Exported getUserConfigDir and transformIssue, added type annotations eliminating implicit any errors

What was dropped (incompatible with new architecture)

  • All apps/backend/ Python changes (backend migrated to TypeScript)
  • Python Graphiti integration changes (now TypeScript-based in apps/desktop/src/main/ai/)
  • Backend test files (test_episode_types.py, test_integration_graphiti.py, test_integration_ollama.py)

Supersedes

Test plan

  • TypeScript compiles cleanly (npx tsc --noEmit — 0 new errors)
  • All new files placed in correct apps/desktop/ paths
  • i18n keys added to both en and fr locales
  • Browser mock updated with all new ElectronAPI methods
  • Preload API wired for multi-repo GitHub methods
  • Manual test: configure custom agent per phase in Settings
  • Manual test: customer project creation and repo cloning
  • Manual test: global MCP health checks in AgentTools

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Customer project flow: create/init customer projects, clone/manage child repos, and multi-repo GitHub (connect, aggregated issues/PRs, detail views).
    • Global MCP management and health checks; Ollama embedding-dimension lookup.
    • Claude agents discovery and catalog; AgentAnalysisBanner for task/phase status.
    • Project index progress events and UI hooks.
  • Improvements

    • Sidebar, GitHub, integrations, Agent tools, and settings UI to support customer/multi-repo and global MCP workflows.
    • Expanded i18n/locales and dialog text for new flows.

…itecture

Port viable frontend changes from PR AndyMik90#1920 to the current apps/desktop/
structure after upstream restructured apps/frontend/ → apps/desktop/ and
migrated backend from Python to TypeScript (Vercel AI SDK v6).

New features ported:
- Custom Agents: IPC handler to read ~/.claude/agents/ definitions,
  specialist agents catalog in AgentProfileSettings
- Global MCP: IPC handler for Claude Code MCP config, health checks,
  pipeline phase assignment UI in AgentTools
- Customer Projects: AddCustomerModal, CustomerReposModal, multi-repo
  GitHub hooks (issues + PRs), RepoFilterDropdown, Sidebar integration
- Memory: Ollama embedding dimension lookup with 30+ model mappings,
  index progress tracking in context store
- i18n: New context namespace, 100+ translation keys (en + fr) for
  settings, dialogs, navigation, tasks

Also fixes code scanning issues from original PR:
- Export getUserConfigDir from claude-code-settings/reader
- Export transformIssue from github/issue-handlers
- Add type annotations to eliminate implicit any errors

Supersedes AndyMik90#1920 (closed due to unmergeable conflicts)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@CLAassistant
Copy link

CLAassistant commented Mar 14, 2026

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you all sign our Contributor License Agreement before we can accept your contribution.
1 out of 2 committers have signed the CLA.

✅ AndyMik90
❌ vitorfgomes
You have signed the CLA already but the status is still pending? Let us recheck it.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 14, 2026

📝 Walkthrough

Walkthrough

Adds customer multi-repo GitHub support, Claude agents discovery, global MCP aggregation and health checks, Ollama embedding-dim lookup, new IPC endpoints/types, new UI components and hooks for customer flows and MCPs, i18n additions, and store updates for index progress.

Changes

Cohort / File(s) Summary
Claude settings & types
apps/desktop/src/main/claude-code-settings/reader.ts, apps/desktop/src/main/claude-code-settings/types.ts
Sanitization/validation for enabledPlugins and mcpServers; getUserConfigDir exported; added enabledPlugins? and mcpServers? to settings types.
Claude MCP & Agents IPC
apps/desktop/src/main/ipc-handlers/claude-mcp-handlers.ts, apps/desktop/src/main/ipc-handlers/claude-agents-handlers.ts, apps/desktop/src/main/ipc-handlers/index.ts
New IPC handlers to aggregate global MCP servers (plugin, inline, ~/.claude.json) and list Claude agents; registration added to handler index.
MCP health & memory handlers
apps/desktop/src/main/ipc-handlers/mcp-handlers.ts, apps/desktop/src/main/ipc-handlers/memory-handlers.ts
Adds global MCP health-check IPC (command/HTTP checks) and Ollama embedding-dimension lookup + IPC endpoint.
GitHub multi-repo IPC & utilities
apps/desktop/src/main/ipc-handlers/github/customer-github-handlers.ts, apps/desktop/src/main/ipc-handlers/github/issue-handlers.ts
New multi-repo GitHub IPC handlers (connection, issues, issue detail, PRs); transformIssue exported for reuse.
Project handlers & IPC constants/types
apps/desktop/src/main/ipc-handlers/project-handlers.ts, apps/desktop/src/shared/constants/ipc.ts, apps/desktop/src/shared/types/ipc.ts
New PROJECT_INIT_CUSTOMER handler; many new IPC channels added; ElectronAPI typings extended (customer init, clone repo, embedding dim, MCP endpoints, index progress/events).
Frontend API & browser mocks
apps/desktop/src/preload/api/modules/github-api.ts, apps/desktop/src/renderer/lib/browser-mock.ts
Preload exposes multi-repo GitHub methods; browser mock extended to stub new endpoints (customer init, clone, embedding dim, MCP, agents, multi-repo).
New/updated React components
apps/desktop/src/renderer/components/AddCustomerModal.tsx, .../CustomerReposModal.tsx, .../AgentTools.tsx, .../AgentProfileSettings.tsx, .../settings/integrations/GitHubIntegration.tsx, .../github-issues/components/RepoFilterDropdown.tsx, .../github-prs/GitHubPRs.tsx, .../ideation/Ideation.tsx, apps/desktop/src/renderer/components/Sidebar.tsx
Add AddCustomerModal, CustomerReposModal, RepoFilterDropdown; extend AgentTools & GitHubIntegration for global MCPs and customer flows; AgentProfileSettings loads agents; Sidebar gains customer context and repo selector; small i18n tweaks.
Multi-repo hooks
apps/desktop/src/renderer/components/github-issues/hooks/useMultiRepoGitHubIssues.ts, apps/desktop/src/renderer/components/github-prs/hooks/useMultiRepoGitHubPRs.ts
New hooks to manage aggregated issues/PRs across multiple repos with id composition utilities, connection checks, and state management.
Store & types
apps/desktop/src/renderer/stores/context-store.ts, apps/desktop/src/shared/types/*, apps/desktop/src/shared/types/integrations.ts, apps/desktop/src/shared/types/project.ts, apps/desktop/src/shared/types/settings.ts, apps/desktop/src/shared/types/task.ts
Context store: index progress fields and force param for refreshProjectIndex. New/shared types for GlobalMcp, Claude agents, multi-repo GitHub, customer project type/child_repos, phase custom agents and globalMcpPhases. Note: some type declarations appear duplicated in integrations file.
i18n
apps/desktop/src/shared/i18n/*
Added context namespace and many EN/FR translations for project index, dialogs (addCustomer, customerRepos), navigation (projectSelector, multiRepo), settings (globalMcps, agents), tasks progress, and related keys.

Sequence Diagram(s)

sequenceDiagram
    participant User as User
    participant Renderer as Renderer (UI)
    participant IPC as Renderer→IPC
    participant Main as Main (Electron)
    participant FS as Filesystem

    User->>Renderer: Open AddCustomerModal / choose folder
    Renderer->>IPC: addProject(path, 'customer') / createFolder
    IPC->>Main: register project
    Main->>FS: mkdir projectPath
    FS-->>Main: success
    Main-->>IPC: project registered
    IPC-->>Renderer: project added
    Renderer->>IPC: initializeCustomerProject(projectId)
    IPC->>Main: create .auto-claude
    Main->>FS: mkdir .auto-claude
    FS-->>Main: success
    Main-->>IPC: initialized
    IPC-->>Renderer: initialized
    Renderer->>User: open/select new project
Loading
sequenceDiagram
    participant Renderer as Renderer (UI)
    participant IPC as Renderer→IPC
    participant PluginCache as Plugin Cache
    participant FS as Filesystem
    participant Resolver as Aggregator

    Renderer->>IPC: getGlobalMcps()
    IPC->>PluginCache: resolvePluginServers()
    PluginCache->>FS: list plugin cache dirs
    FS-->>PluginCache: dir metadata
    PluginCache->>FS: read .mcp.json for latest hash
    FS-->>PluginCache: plugin server entries
    IPC->>FS: read settings.mcpServers (inline)
    FS-->>IPC: inline entries
    IPC->>FS: read ~/.claude.json
    FS-->>IPC: claude.json entries
    IPC->>Resolver: aggregate plugin + inline + claude.json
    Resolver-->>Renderer: GlobalMcpInfo
Loading
sequenceDiagram
    participant Renderer as Renderer (UI)
    participant IPC as Renderer→IPC
    participant FS as Filesystem
    participant GH as GitHub API

    Renderer->>IPC: checkMultiRepoConnection(customerId)
    IPC->>FS: read customer .env / child repo configs / git remotes
    FS-->>IPC: repo list
    Renderer->>IPC: getMultiRepoIssues(customerId, state, page)
    IPC->>GH: parallel fetch issues per repo
    GH-->>IPC: issues per repo
    IPC-->>Renderer: aggregated issues + repos
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

Suggested labels

area/frontend, size/XL

Poem

🐰 I hopped through code to open a door,
Customers, repos, agents—and MCPs galore.
Health checks hum while embeddings align,
New hooks and modals stitched, one commit at a time. 🌿

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 78.26% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately reflects the main change: porting memory system and custom agents functionality to the desktop architecture, which is the primary objective of this PR.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
📝 Coding Plan
  • Generate coding plan for human review comments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly updates the desktop application by porting critical frontend features from a previous, unmergeable branch to the new apps/desktop/ architecture. The changes introduce robust support for custom AI agents, global MCP configurations, and a comprehensive system for managing customer projects with multi-repository GitHub integration. These updates enhance the application's ability to handle complex project structures and improve the user experience for managing diverse development workflows.

Highlights

  • Custom Agents Integration: Implemented IPC handlers to read custom agent definitions from ~/.claude/agents/ and integrated a specialist agents catalog into the AgentProfileSettings UI.
  • Global MCP Support: Added IPC handlers for Claude Code Global MCP configuration, including health checks and a UI for assigning pipeline phases in AgentTools.
  • Customer Projects: Introduced new modals (AddCustomerModal, CustomerReposModal), multi-repo GitHub hooks for issues and PRs, a RepoFilterDropdown, and integrated these features into the Sidebar.
  • Memory Improvements: Added Ollama embedding dimension lookup for over 30 models and implemented index progress tracking within the context store.
  • Internationalization (i18n): Added a new context namespace and over 100 new translation keys for settings, dialogs, navigation, and tasks in both English and French.
  • Type Safety Enhancements: Exported getUserConfigDir and transformIssue functions and added type annotations to eliminate implicit any errors, improving overall code robustness.
  • Dropped Features: Removed all Python backend changes, Python Graphiti integration, and associated backend test files due to the migration to a TypeScript-based backend and new desktop architecture.
Changelog
  • apps/desktop/src/main/claude-code-settings/reader.ts
    • Added validation and sanitization for enabledPlugins and mcpServers fields in Claude Code settings.
    • Exported the getUserConfigDir function for broader use.
  • apps/desktop/src/main/claude-code-settings/types.ts
    • Added enabledPlugins and mcpServers properties to the ClaudeCodeSettings interface.
  • apps/desktop/src/main/ipc-handlers/claude-agents-handlers.ts
    • Added new IPC handlers for reading custom agent definitions from the ~/.claude/agents/ directory.
  • apps/desktop/src/main/ipc-handlers/claude-mcp-handlers.ts
    • Added new IPC handlers for resolving global MCP configurations from settings, plugins, and ~/.claude.json.
  • apps/desktop/src/main/ipc-handlers/github/customer-github-handlers.ts
    • Added new IPC handlers to manage multi-repository GitHub issues and pull requests for customer projects.
  • apps/desktop/src/main/ipc-handlers/github/issue-handlers.ts
    • Exported the transformIssue function to allow external usage.
  • apps/desktop/src/main/ipc-handlers/index.ts
    • Registered the new Claude Agents and Claude MCP IPC handlers.
  • apps/desktop/src/main/ipc-handlers/mcp-handlers.ts
    • Added a new checkGlobalMcpHealth function for global MCPs, bypassing command allowlist validation.
    • Registered a new IPC channel for checking global MCP health.
  • apps/desktop/src/main/ipc-handlers/memory-handlers.ts
    • Added a KNOWN_OLLAMA_EMBEDDING_DIMS map and lookupEmbeddingDim function for Ollama model dimension lookup.
    • Registered a new IPC channel for retrieving Ollama embedding dimensions.
  • apps/desktop/src/main/ipc-handlers/project-handlers.ts
    • Imported mkdirSync and path modules.
    • Replaced console.log with debugLog for tab state logging.
    • Added a new PROJECT_INIT_CUSTOMER IPC handler for initializing customer projects without Git.
  • apps/desktop/src/preload/api/modules/github-api.ts
    • Added new IPC methods for checking multi-repo GitHub connections, getting multi-repo issues, issue details, and PRs.
  • apps/desktop/src/renderer/components/AddCustomerModal.tsx
    • Added a new component for creating or opening customer project folders.
  • apps/desktop/src/renderer/components/AgentTools.tsx
    • Integrated UI for displaying and managing global MCP configurations, including health checks and phase assignments.
  • apps/desktop/src/renderer/components/CustomerReposModal.tsx
    • Added a new component for listing and cloning GitHub repositories into a customer project's directory.
  • apps/desktop/src/renderer/components/GitHubSetupModal.tsx
    • Modified the GitHub setup flow to accommodate customer projects, allowing token-only setup for customers.
  • apps/desktop/src/renderer/components/Sidebar.tsx
    • Implemented logic to identify and display customer project contexts and their child repositories.
    • Added a repository selector dropdown for customer projects.
    • Updated Git status checks to skip for customer-type projects and track dismissed Git setup modals.
  • apps/desktop/src/renderer/components/github-issues/components/RepoFilterDropdown.tsx
    • Added a new component for filtering GitHub issues by repository in multi-repo contexts.
  • apps/desktop/src/renderer/components/github-issues/hooks/useMultiRepoGitHubIssues.ts
    • Added a new hook for managing and fetching GitHub issues across multiple repositories for customer projects.
  • apps/desktop/src/renderer/components/github-prs/GitHubPRs.tsx
    • Updated the NotConnectedState component to support translation options.
  • apps/desktop/src/renderer/components/github-prs/hooks/useMultiRepoGitHubPRs.ts
    • Added a new hook for managing and fetching GitHub pull requests across multiple repositories for customer projects.
  • apps/desktop/src/renderer/components/ideation/Ideation.tsx
    • Updated the Ideation component to use i18n for the 'No ideas to display' message.
  • apps/desktop/src/renderer/components/settings/AgentProfileSettings.tsx
    • Added UI to display available specialist agents loaded from ~/.claude/agents/.
  • apps/desktop/src/renderer/components/settings/integrations/GitHubIntegration.tsx
    • Modified the GitHub integration settings to include UI for cloning repositories into customer project folders.
  • apps/desktop/src/renderer/lib/browser-mock.ts
    • Added mocks for new IPC channels related to customer project initialization, context index progress, Ollama embedding dimensions, global MCPs, and Claude agents for browser mode.
  • apps/desktop/src/renderer/stores/context-store.ts
    • Added state and actions for tracking project index progress (message, current, total).
    • Updated refreshProjectIndex to accept an optional force parameter and reset index progress.
  • apps/desktop/src/shared/constants/ipc.ts
    • Added new IPC channels: PROJECT_INIT_CUSTOMER, CONTEXT_INDEX_PROGRESS, multi-repo GitHub operations (GITHUB_CHECK_MULTI_REPO_CONNECTION, GITHUB_GET_MULTI_REPO_ISSUES, GITHUB_GET_MULTI_REPO_ISSUE_DETAIL, GITHUB_GET_MULTI_REPO_PRS), GITHUB_CLONE_REPO, OLLAMA_GET_EMBEDDING_DIM, MCP_CHECK_GLOBAL_HEALTH, CLAUDE_MCP_GET_GLOBAL, and CLAUDE_AGENTS_GET.
  • apps/desktop/src/shared/i18n/index.ts
    • Added the context namespace to the i18n configuration for both English and French.
  • apps/desktop/src/shared/i18n/locales/en/common.json
    • Added new translation keys for common UI elements, multi-repo messages, insights placeholder, ideation, and issues.
  • apps/desktop/src/shared/i18n/locales/en/context.json
    • Added a new English translation file for project index related strings.
  • apps/desktop/src/shared/i18n/locales/en/dialogs.json
    • Added new English translation keys for addCustomer and customerRepos modals, and envConfig errors.
  • apps/desktop/src/shared/i18n/locales/en/navigation.json
    • Added new English translation keys for project selector and multi-repo features.
  • apps/desktop/src/shared/i18n/locales/en/settings.json
    • Added new English translation keys for zoom controls, color theme details, project general description, agent profile available agents, global MCPs, and GitHub integration.
  • apps/desktop/src/shared/i18n/locales/en/tasks.json
    • Added new English translation keys for task progress display.
  • apps/desktop/src/shared/i18n/locales/fr/common.json
    • Added French translations for new common UI elements, multi-repo messages, insights placeholder, ideation, and issues.
  • apps/desktop/src/shared/i18n/locales/fr/context.json
    • Added a new French translation file for project index related strings.
  • apps/desktop/src/shared/i18n/locales/fr/dialogs.json
    • Added new French translation keys for addCustomer and customerRepos modals, and envConfig errors.
  • apps/desktop/src/shared/i18n/locales/fr/navigation.json
    • Added new French translation keys for project selector and multi-repo features.
  • apps/desktop/src/shared/i18n/locales/fr/settings.json
    • Added new French translation keys for zoom controls, color theme details, project general description, agent profile available agents, global MCPs, and GitHub integration.
  • apps/desktop/src/shared/i18n/locales/fr/tasks.json
    • Added new French translation keys for task progress display.
  • apps/desktop/src/shared/types/integrations.ts
    • Added new interfaces: MultiRepoGitHubStatus, MultiRepoIssuesResult, MultiRepoPRData, MultiRepoPRsResult for multi-repo GitHub operations.
    • Added new interfaces: GlobalMcpServerEntry, GlobalMcpInfo for global MCP configurations.
    • Added new interfaces: ClaudeCustomAgent, ClaudeAgentCategory, ClaudeAgentsInfo for custom agent definitions.
  • apps/desktop/src/shared/types/ipc.ts
    • Updated ElectronAPI interface to include new IPC methods for project management (addProject with type, initializeCustomerProject), context operations (refreshProjectIndex with force, onIndexProgress), GitHub operations (cloneGitHubRepo, multi-repo methods), Ollama embedding dimensions, global MCPs, and Claude agents.
  • apps/desktop/src/shared/types/project.ts
    • Added an optional type field ('project' or 'customer') to the Project interface.
    • Extended ProjectIndex to include a 'customer' project type and an optional child_repos field.
    • Added 'documentation' as a possible service type in ServiceInfo.
    • Added an optional env field to CustomMcpServer for environment variables.
  • apps/desktop/src/shared/types/settings.ts
    • Added new interfaces PhaseCustomAgentsConfig and GlobalMcpPhaseConfig for phase-based custom agent and global MCP assignments.
    • Included phaseCustomAgents and globalMcpPhases in the AppSettings interface.
  • apps/desktop/src/shared/types/task.ts
    • Added phaseCustomAgents to the TaskMetadata interface for per-phase custom agent IDs.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request is a significant feature port, bringing custom agents, a memory system, and multi-repo customer project support to the new desktop architecture. The changes are extensive, adding new IPC handlers, frontend components, and updating type definitions.

My review focuses on the new backend logic in the main process. I've identified several instances where synchronous file system operations are used within asynchronous IPC handlers. While this might work for small files/directories, it can block the main process and lead to UI unresponsiveness. I recommend switching to asynchronous file system methods to improve performance and robustness.

Additionally, I've pointed out a case where an error is caught but not logged, which can hinder debugging. I've also included a suggestion to refactor a piece of validation logic for better conciseness and maintainability.

Overall, the PR is well-structured, and the new features are a great addition. Addressing these points will help ensure the application remains responsive and maintainable.

Comment on lines +153 to +163
const plugins: Record<string, boolean> = {};
let hasValidPlugins = false;
for (const [key, value] of Object.entries(obj.enabledPlugins as Record<string, unknown>)) {
if (typeof value === 'boolean') {
plugins[key] = value;
hasValidPlugins = true;
}
}
if (hasValidPlugins) {
sanitized.enabledPlugins = plugins;
hasValidFields = true;
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

This block of code for sanitizing enabledPlugins can be made more concise and declarative by using Object.fromEntries and Array.prototype.filter.

      const plugins = Object.fromEntries(
        Object.entries(obj.enabledPlugins as Record<string, unknown>).filter(
          ([, value]) => typeof value === 'boolean'
        )
      );

      if (Object.keys(plugins).length > 0) {
        sanitized.enabledPlugins = plugins as Record<string, boolean>;
        hasValidFields = true;
      }

const categories: ClaudeAgentCategory[] = [];
let totalAgents = 0;

const entries = readdirSync(agentsDir, { withFileTypes: true });
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

Using synchronous file system methods like readdirSync in an async handler can block the main process, impacting UI responsiveness. Consider using the asynchronous version fs.promises.readdir along with Promise.all to process directories concurrently.

const envPath = path.join(customer.path, customer.autoBuildPath, '.env');
if (existsSync(envPath)) {
try {
const content = readFileSync(envPath, 'utf-8');
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

Using readFileSync in an async function blocks the main process, which can lead to UI unresponsiveness. Since this function is already async, it's better to use the asynchronous version fs.promises.readFile. This will require updating the fs import at the top of the file to use fs/promises.

Comment on lines +128 to +130
} catch {
debugLog('[Customer GitHub] Could not detect git remote for child:', child.path);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

The error object is not being logged in this catch block, which can make debugging difficult. It's a good practice to always log the error that was caught.

Suggested change
} catch {
debugLog('[Customer GitHub] Could not detect git remote for child:', child.path);
}
} catch (error) {
debugLog('[Customer GitHub] Could not detect git remote for child:', child.path, error);
}

const dotAutoClaude = path.join(project.path, '.auto-claude');

if (!existsSync(dotAutoClaude)) {
mkdirSync(dotAutoClaude, { recursive: true });
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

Using mkdirSync in an async handler can block the main process. It's better to use the asynchronous version from fs/promises. This would require updating the fs import at the top of the file.

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

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/desktop/src/main/ipc-handlers/github/customer-github-handlers.ts`:
- Around line 368-388: Extract the inline mapping that builds `transformed` into
a new typed helper function `transformPullRequest(pr: GitHubAPIPullRequest,
repoFullName: string)` and add a `GitHubAPIPullRequest` interface modeling the
fields used (number, title, body, state, user.login, head.ref, base.ref,
additions, deletions, changed_files, assignees, created_at, updated_at,
html_url); replace the inline map with `(data as any[]).map(pr =>
transformPullRequest(pr, repoFullName))` and import/align usage like the
existing `transformIssue` helper so the PR transformation is reusable and
type-safe.
- Around line 193-195: The code currently reassigns the parameter page; instead
create a new local constant (e.g., validatedPage) and perform the checks on page
to set validatedPage = 1 when invalid, then use validatedPage instead of page
when building the GitHub API endpoint (replace the usage in the endpoint
construction where page is interpolated); update any other references in the
function that rely on the sanitized page value to use validatedPage so the
original parameter is not mutated.

In `@apps/desktop/src/renderer/components/AddCustomerModal.tsx`:
- Around line 139-142: The platform separator detection using
window.navigator.platform is deprecated; update the logic in AddCustomerModal
(symbols: sep and folderPreview) to prefer a separator provided by the preload
API (e.g., window.electronAPI.pathSep) and fall back to inferring the separator
from the provided location string (check if location includes '\\' then use '\\'
else '/'), so set sep from window.electronAPI.pathSep ??
(location?.includes('\\') ? '\\' : '/') before building folderPreview; ensure
you preserve the existing behavior when customerName.trim() and location are
present.

In `@apps/desktop/src/renderer/components/AgentTools.tsx`:
- Around line 764-769: The useEffect that auto-checks health can re-run if the
identity of checkGlobalMcpHealth changes; to guard against unnecessary loops,
update the effect to explicitly depend on allGlobalServers (and optionally keep
checkGlobalMcpHealth) and add an internal guard that returns early unless
allGlobalServers.length > 0 before calling checkGlobalMcpHealth; reference the
existing useEffect, the allGlobalServers value, and the checkGlobalMcpHealth
callback when making this change.
- Line 1477: The inline fallback string in the t(...) call inside AgentTools.tsx
uses 'No global MCP servers found.' — remove that inline English fallback and
ensure the translation key mcp.globalMcps.noServers is defined in the
translation files; add the corresponding entry to en/settings.json and
fr/settings.json with appropriate localized strings, then call
t('settings:mcp.globalMcps.noServers') (no inline fallback) in the component to
rely solely on react-i18next.

In `@apps/desktop/src/renderer/components/CustomerReposModal.tsx`:
- Line 140: Replace the hardcoded aria-label in CustomerReposModal.tsx with a
react-i18next translation key: import and use the useTranslation hook (t) in the
component and change aria-label="Clear search" to
aria-label={t('dialogs:clearSearch')} (or another clear key name), then add the
same key and localized strings to en/dialogs.json and fr/dialogs.json (e.g.
"clearSearch": "Clear search" and French translation) so the label is localized
across locales.

In
`@apps/desktop/src/renderer/components/github-issues/components/RepoFilterDropdown.tsx`:
- Around line 3-9: Replace the relative import for the UI select components with
the project path alias: change the import of Select, SelectContent, SelectItem,
SelectTrigger, SelectValue (currently from '../../ui/select') to use the
configured alias (e.g. '@components/ui/select' or '@/components/ui/select' per
renderer tsconfig). Update the import statement in RepoFilterDropdown.tsx to the
aliased path and ensure your tsconfig/webpack supports the chosen alias so the
module resolves.

In
`@apps/desktop/src/renderer/components/github-issues/hooks/useMultiRepoGitHubIssues.ts`:
- Around line 195-215: The callbacks getFilteredIssues and getOpenIssuesCount
are recreating on any state change because they depend on the whole state
object; to fix, move the destructuring const { issues, selectedRepo } = state
into the surrounding scope (above the useCallback definitions) and change the
useCallback dependencies to the specific pieces: [issues, selectedRepo]; update
both getFilteredIssues and getOpenIssuesCount to use those outer-scope variables
so their dependency arrays reference only issues and selectedRepo rather than
state.
- Around line 90-132: Destructure the specific pieces of state used by the
effect (e.g. const { filterState } = state and const syncConnected =
state.syncStatus?.connected) and then update the useEffect to depend on
customerId, filterState, syncConnected, and t instead of referencing
state.filterState and state.syncStatus?.connected directly; keep the existing
loadIssues, setState calls, and cancelled flag logic but ensure the effect
closure only captures those destructured primitives to avoid stale closures and
unnecessary reruns.

In
`@apps/desktop/src/renderer/components/github-prs/hooks/useMultiRepoGitHubPRs.ts`:
- Around line 215-216: The isMultiRepo flag is hardcoded to true; change it to
derive from the actual repository list/count in the hook (useMultiRepoGitHubPRs)
— e.g., compute isMultiRepo = (repos?.length ?? repoCount ?? 0) > 1 using the
existing repo array or count variable used earlier in the hook, and return that
value instead of the literal true so the flag reflects the real multi-repo
state.

In `@apps/desktop/src/renderer/components/GitHubSetupModal.tsx`:
- Around line 130-137: The code uses an unnecessary type assertion "(project as
Project & { type?: string }).type" when checking project.type; remove the cast
and use the existing Project type directly (i.e., reference project.type) in the
condition where hasGitHubAuth is checked, keeping the surrounding logic that
calls onComplete with ghTokenResult.data!.token, githubRepo, mainBranch and
githubAuthMethod unchanged; update the condition that references hasGitHubAuth
and project.type to simply use project.type (and ensure imports/definitions
still reference the Project interface).
- Around line 252-261: The code in the GitHubSetupModal component uses a
redundant type assertion when checking project type; simplify the conditional by
accessing project.type directly (remove the unnecessary "(project as Project & {
type?: string })" cast) in the block that calls onComplete with
githubToken/githubRepo/mainBranch/githubAuthMethod for customers, keeping the
existing call to onComplete and the early return unchanged.

In
`@apps/desktop/src/renderer/components/settings/integrations/GitHubIntegration.tsx`:
- Around line 432-566: Replace the hardcoded English UI strings in the
customer-specific clone section of GitHubIntegration (the JSX that renders
Cloned Repositories, Clone Repositories, button labels, search placeholder, and
per-repo status labels) with the corresponding react-i18next keys from the
settings:github namespace (e.g., use t('settings:github.clonedRepositories'),
t('settings:github.cloneRepositories'), t('settings:github.refresh') /
t('settings:github.loadRepos'), t('settings:github.searchRepos'),
t('settings:github.cloned'), t('settings:github.cloning'),
t('settings:github.clone')). Ensure the component uses the useTranslation hook
(t) already present in the file and update strings in the JSX blocks that
reference customerChildProjects, loadCustomerRepos, customerRepoSearch,
filteredCustomerRepos, cloneStatuses, cloneErrors, and handleCloneRepo so all
user-facing text uses t(...).

In `@apps/desktop/src/renderer/components/Sidebar.tsx`:
- Around line 140-146: Remove the redundant broad type assertions around
selectedProject and project elements; directly use the existing Project.type
optional union. In the memo that computes parentCustomer (references:
selectedProject, projects, normalizePath, parentCustomer), replace occurrences
like (selectedProject as Project & { type?: string }).type with
selectedProject.type and similarly remove casts on p in the projects.find
callback so you compare p.type === 'customer' and rely on TypeScript's Project
definition; ensure null/undefined checks remain for optional type where needed.

In `@apps/desktop/src/renderer/lib/browser-mock.ts`:
- Around line 144-145: The mock function checkGlobalMcpHealth currently types
its parameter as any; update its signature to use the CustomMcpServer type so it
matches the ElectronAPI interface and allows using server.id safely—locate
checkGlobalMcpHealth in browser-mock.ts and replace the server parameter type
with CustomMcpServer (importing or referencing CustomMcpServer from the shared
types if necessary) to align the mock with the interface.

In `@apps/desktop/src/shared/types/task.ts`:
- Line 239: Replace the inline type import used for phaseCustomAgents with a
named import from ./settings to keep import style consistent: add
PhaseCustomAgentsConfig to the existing import list from './settings' (the same
import that currently brings in other types) and then change the property
declaration phaseCustomAgents?: import('./settings').PhaseCustomAgentsConfig; to
phaseCustomAgents?: PhaseCustomAgentsConfig; so the file consistently uses the
named type import.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: e9c4d2a9-0a41-489e-b93f-f1867ee216e9

📥 Commits

Reviewing files that changed from the base of the PR and between 53b5546 and 7983214.

📒 Files selected for processing (44)
  • apps/desktop/src/main/claude-code-settings/reader.ts
  • apps/desktop/src/main/claude-code-settings/types.ts
  • apps/desktop/src/main/ipc-handlers/claude-agents-handlers.ts
  • apps/desktop/src/main/ipc-handlers/claude-mcp-handlers.ts
  • apps/desktop/src/main/ipc-handlers/github/customer-github-handlers.ts
  • apps/desktop/src/main/ipc-handlers/github/issue-handlers.ts
  • apps/desktop/src/main/ipc-handlers/index.ts
  • apps/desktop/src/main/ipc-handlers/mcp-handlers.ts
  • apps/desktop/src/main/ipc-handlers/memory-handlers.ts
  • apps/desktop/src/main/ipc-handlers/project-handlers.ts
  • apps/desktop/src/preload/api/modules/github-api.ts
  • apps/desktop/src/renderer/components/AddCustomerModal.tsx
  • apps/desktop/src/renderer/components/AgentTools.tsx
  • apps/desktop/src/renderer/components/CustomerReposModal.tsx
  • apps/desktop/src/renderer/components/GitHubSetupModal.tsx
  • apps/desktop/src/renderer/components/Sidebar.tsx
  • apps/desktop/src/renderer/components/github-issues/components/RepoFilterDropdown.tsx
  • apps/desktop/src/renderer/components/github-issues/hooks/useMultiRepoGitHubIssues.ts
  • apps/desktop/src/renderer/components/github-prs/GitHubPRs.tsx
  • apps/desktop/src/renderer/components/github-prs/hooks/useMultiRepoGitHubPRs.ts
  • apps/desktop/src/renderer/components/ideation/Ideation.tsx
  • apps/desktop/src/renderer/components/settings/AgentProfileSettings.tsx
  • apps/desktop/src/renderer/components/settings/integrations/GitHubIntegration.tsx
  • apps/desktop/src/renderer/lib/browser-mock.ts
  • apps/desktop/src/renderer/stores/context-store.ts
  • apps/desktop/src/shared/constants/ipc.ts
  • apps/desktop/src/shared/i18n/index.ts
  • apps/desktop/src/shared/i18n/locales/en/common.json
  • apps/desktop/src/shared/i18n/locales/en/context.json
  • apps/desktop/src/shared/i18n/locales/en/dialogs.json
  • apps/desktop/src/shared/i18n/locales/en/navigation.json
  • apps/desktop/src/shared/i18n/locales/en/settings.json
  • apps/desktop/src/shared/i18n/locales/en/tasks.json
  • apps/desktop/src/shared/i18n/locales/fr/common.json
  • apps/desktop/src/shared/i18n/locales/fr/context.json
  • apps/desktop/src/shared/i18n/locales/fr/dialogs.json
  • apps/desktop/src/shared/i18n/locales/fr/navigation.json
  • apps/desktop/src/shared/i18n/locales/fr/settings.json
  • apps/desktop/src/shared/i18n/locales/fr/tasks.json
  • apps/desktop/src/shared/types/integrations.ts
  • apps/desktop/src/shared/types/ipc.ts
  • apps/desktop/src/shared/types/project.ts
  • apps/desktop/src/shared/types/settings.ts
  • apps/desktop/src/shared/types/task.ts

Comment on lines +193 to +195
if (typeof page !== 'number' || !Number.isFinite(page) || page < 1) {
page = 1;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Avoid reassigning function parameters.

Reassigning page can lead to confusion and may be flagged by strict linters. Use a local variable instead.

♻️ Proposed fix
-      if (typeof page !== 'number' || !Number.isFinite(page) || page < 1) {
-        page = 1;
-      }
+      const validatedPage = (typeof page !== 'number' || !Number.isFinite(page) || page < 1) ? 1 : page;

Then use validatedPage in the endpoint construction on line 217.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/desktop/src/main/ipc-handlers/github/customer-github-handlers.ts` around
lines 193 - 195, The code currently reassigns the parameter page; instead create
a new local constant (e.g., validatedPage) and perform the checks on page to set
validatedPage = 1 when invalid, then use validatedPage instead of page when
building the GitHub API endpoint (replace the usage in the endpoint construction
where page is interpolated); update any other references in the function that
rely on the sanitized page value to use validatedPage so the original parameter
is not mutated.

Comment on lines +368 to +388
// TODO: Add a typed interface (e.g. GitHubAPIPullRequest) for the GitHub PR API response shape
// biome-ignore lint/suspicious/noExplicitAny: GitHub REST API response shape
const transformed = (data as any[]).map((pr) => ({
number: pr.number,
title: pr.title,
body: pr.body || '',
state: pr.state.toLowerCase(),
author: { login: pr.user.login },
headRefName: pr.head.ref,
baseRefName: pr.base.ref,
additions: pr.additions ?? 0,
deletions: pr.deletions ?? 0,
changedFiles: pr.changed_files ?? 0,
// biome-ignore lint/suspicious/noExplicitAny: GitHub REST API assignee shape
assignees: (pr.assignees || []).map((a: any) => ({ login: a.login })),
createdAt: pr.created_at,
updatedAt: pr.updated_at,
htmlUrl: pr.html_url,
repoFullName,
}));
allPRs.push(...transformed);
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Consider extracting PR transformation to a typed helper.

Similar to how transformIssue is imported and used for issues, consider creating a transformPullRequest helper with a proper GitHubAPIPullRequest interface to improve type safety and reusability.

Do you want me to help create a typed interface and transformation function for GitHub PRs?

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/desktop/src/main/ipc-handlers/github/customer-github-handlers.ts` around
lines 368 - 388, Extract the inline mapping that builds `transformed` into a new
typed helper function `transformPullRequest(pr: GitHubAPIPullRequest,
repoFullName: string)` and add a `GitHubAPIPullRequest` interface modeling the
fields used (number, title, body, state, user.login, head.ref, base.ref,
additions, deletions, changed_files, assignees, created_at, updated_at,
html_url); replace the inline map with `(data as any[]).map(pr =>
transformPullRequest(pr, repoFullName))` and import/align usage like the
existing `transformIssue` helper so the PR transformation is reusable and
type-safe.

Comment on lines +139 to +142
const sep = window.navigator.platform.startsWith('Win') ? '\\' : '/';
const folderPreview = customerName.trim() && location
? `${location}${sep}${customerName.trim()}`
: null;
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Platform detection could be more robust.

The current approach works but navigator.platform is deprecated. Consider using a more reliable method.

✨ Alternative approach
-  const sep = window.navigator.platform.startsWith('Win') ? '\\' : '/';
+  // Use path.sep if available via preload, or detect from existing path
+  const sep = location.includes('\\') ? '\\' : '/';

Or if the preload API exposes path.sep:

const sep = window.electronAPI.pathSep ?? (location.includes('\\') ? '\\' : '/');
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/desktop/src/renderer/components/AddCustomerModal.tsx` around lines 139 -
142, The platform separator detection using window.navigator.platform is
deprecated; update the logic in AddCustomerModal (symbols: sep and
folderPreview) to prefer a separator provided by the preload API (e.g.,
window.electronAPI.pathSep) and fall back to inferring the separator from the
provided location string (check if location includes '\\' then use '\\' else
'/'), so set sep from window.electronAPI.pathSep ?? (location?.includes('\\') ?
'\\' : '/') before building folderPreview; ensure you preserve the existing
behavior when customerName.trim() and location are present.

Comment on lines +764 to +769
// Auto-check health when global MCPs are loaded
useEffect(() => {
if (allGlobalServers.length > 0) {
checkGlobalMcpHealth();
}
}, [checkGlobalMcpHealth]);
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Potential infinite re-check loop if health check modifies state improperly.

The effect depends on checkGlobalMcpHealth, which itself depends on allGlobalServers. While the current implementation is correct because allGlobalServers is memoized and won't change unless globalMcps changes, consider adding a guard to prevent unnecessary re-checks.

💡 Optional: Add explicit guard
   // Auto-check health when global MCPs are loaded
   useEffect(() => {
-    if (allGlobalServers.length > 0) {
+    if (allGlobalServers.length > 0 && !isCheckingGlobalHealth) {
       checkGlobalMcpHealth();
     }
-  }, [checkGlobalMcpHealth]);
+  }, [allGlobalServers]); // Depend on the data, not the callback

This makes the dependency more explicit and prevents potential issues if the callback identity changes unexpectedly.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/desktop/src/renderer/components/AgentTools.tsx` around lines 764 - 769,
The useEffect that auto-checks health can re-run if the identity of
checkGlobalMcpHealth changes; to guard against unnecessary loops, update the
effect to explicitly depend on allGlobalServers (and optionally keep
checkGlobalMcpHealth) and add an internal guard that returns early unless
allGlobalServers.length > 0 before calling checkGlobalMcpHealth; reference the
existing useEffect, the allGlobalServers value, and the checkGlobalMcpHealth
callback when making this change.

<div className="space-y-2">
{allGlobalServers.length === 0 && (
<p className="text-sm text-muted-foreground text-center py-3">
{t('settings:mcp.globalMcps.noServers', 'No global MCP servers found.')}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Inline fallback string should be moved to translation files.

Using an inline fallback string 'No global MCP servers found.' is inconsistent with the rest of the codebase that relies solely on translation keys. This fallback will show in English for all locales if the key is missing.

🌐 Proposed fix
-                   {t('settings:mcp.globalMcps.noServers', 'No global MCP servers found.')}
+                   {t('settings:mcp.globalMcps.noServers')}

Ensure the key mcp.globalMcps.noServers exists in both en/settings.json and fr/settings.json.

As per coding guidelines: "All frontend user-facing text must use react-i18next translation keys. Add keys to both en/*.json and fr/*.json translation files."

📝 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
{t('settings:mcp.globalMcps.noServers', 'No global MCP servers found.')}
{t('settings:mcp.globalMcps.noServers')}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/desktop/src/renderer/components/AgentTools.tsx` at line 1477, The inline
fallback string in the t(...) call inside AgentTools.tsx uses 'No global MCP
servers found.' — remove that inline English fallback and ensure the translation
key mcp.globalMcps.noServers is defined in the translation files; add the
corresponding entry to en/settings.json and fr/settings.json with appropriate
localized strings, then call t('settings:mcp.globalMcps.noServers') (no inline
fallback) in the component to rely solely on react-i18next.

Comment on lines +252 to +261
// For customers, we only need the GitHub token -- skip repo/branch/claude steps
if ((project as Project & { type?: string }).type === 'customer') {
onComplete({
githubToken: token,
githubRepo: '',
mainBranch: '',
githubAuthMethod: 'oauth'
});
return;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Same redundant type assertion.

Apply the same simplification here.

✨ Proposed simplification
     // For customers, we only need the GitHub token -- skip repo/branch/claude steps
-    if ((project as Project & { type?: string }).type === 'customer') {
+    if (project.type === 'customer') {
       onComplete({
         githubToken: token,
         githubRepo: '',
         mainBranch: '',
         githubAuthMethod: 'oauth'
       });
       return;
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/desktop/src/renderer/components/GitHubSetupModal.tsx` around lines 252 -
261, The code in the GitHubSetupModal component uses a redundant type assertion
when checking project type; simplify the conditional by accessing project.type
directly (remove the unnecessary "(project as Project & { type?: string })"
cast) in the block that calls onComplete with
githubToken/githubRepo/mainBranch/githubAuthMethod for customers, keeping the
existing call to onComplete and the early return unchanged.

Comment on lines +432 to +566
{/* Customer-specific: Clone Repositories */}
{projectType === 'customer' && envConfig.githubToken && (
<>
<Separator />

{/* Default Branch Selector */}
{projectPath && (
<div className="space-y-2">
<div className="flex items-center justify-between">
<div className="space-y-0.5">
<div className="flex items-center gap-2">
<GitBranch className="h-4 w-4 text-info" />
<Label className="text-sm font-medium text-foreground">
{t('settings:projectSections.github.defaultBranch.label')}
</Label>
{/* Already cloned repos */}
{customerChildProjects.length > 0 && (
<div className="space-y-2">
<Label className="text-sm font-medium text-foreground flex items-center gap-2">
<FolderGit2 className="h-4 w-4" />
Cloned Repositories
</Label>
<div className="space-y-1.5">
{customerChildProjects.map((child) => (
<div
key={child.id}
className="flex items-center gap-2 rounded-md border border-border bg-muted/50 px-3 py-2 text-sm"
>
<CheckCircle2 className="h-4 w-4 text-green-500 shrink-0" />
<span className="truncate font-medium">{child.name}</span>
<span className="text-xs text-muted-foreground truncate ml-auto">{child.path}</span>
</div>
))}
</div>
<p className="text-xs text-muted-foreground pl-6">
{t('settings:projectSections.github.defaultBranch.description')}
</p>
</div>
<Button
variant="ghost"
size="sm"
onClick={fetchBranches}
disabled={isLoadingBranches}
className="h-7 px-2"
>
<RefreshCw className={`h-3 w-3 ${isLoadingBranches ? 'animate-spin' : ''}`} />
</Button>
</div>
)}

{branchesError && (
<div className="flex items-center gap-2 text-xs text-destructive pl-6">
<AlertCircle className="h-3 w-3" />
{branchesError}
{/* Clone new repos */}
<div className="space-y-3">
<div className="flex items-center justify-between">
<Label className="text-sm font-medium text-foreground flex items-center gap-2">
<Download className="h-4 w-4" />
Clone Repositories
</Label>
<Button
variant="outline"
size="sm"
onClick={loadCustomerRepos}
disabled={isLoadingCustomerRepos}
>
{isLoadingCustomerRepos ? (
<Loader2 className="mr-1.5 h-3.5 w-3.5 animate-spin" />
) : (
<RefreshCw className="mr-1.5 h-3.5 w-3.5" />
)}
{customerRepos.length > 0 ? 'Refresh' : 'Load Repos'}
</Button>
</div>
)}

<div className="pl-6">
<Combobox
options={branchOptions}
value={selectedBranch}
onValueChange={handleBranchChange}
placeholder={t('settings:projectSections.github.defaultBranch.autoDetect')}
searchPlaceholder={t('settings:projectSections.github.defaultBranch.searchPlaceholder')}
emptyMessage={t('settings:projectSections.github.defaultBranch.noBranchesFound')}
disabled={isLoadingBranches}
className="w-full"
/>
{customerReposError && (
<div className="flex items-center gap-2 text-xs text-destructive">
<AlertCircle className="h-3 w-3" />
{customerReposError}
</div>
)}

{customerRepos.length > 0 && (
<>
{/* Search */}
<Input
type="text"
value={customerRepoSearch}
onChange={(e) => setCustomerRepoSearch(e.target.value)}
placeholder="Search repositories..."
className="h-8 text-xs"
/>

{/* Repo list */}
<div className="space-y-1.5 max-h-[300px] overflow-y-auto">
{filteredCustomerRepos.map((repo) => {
const status = cloneStatuses[repo.fullName] || 'idle';
const alreadyCloned = customerChildProjects.some(
p => p.name === repo.fullName.split('/').pop()
);
const cloneError = cloneErrors[repo.fullName];

return (
<div
key={repo.fullName}
className={cn(
'flex items-center gap-2 rounded-md border px-3 py-2 text-sm',
status === 'done' || alreadyCloned
? 'border-green-500/30 bg-green-500/5'
: 'border-border'
)}
>
<Github className="h-4 w-4 shrink-0 text-muted-foreground" />
<div className="flex-1 min-w-0">
<div className="flex items-center gap-1.5">
<span className="text-xs font-medium truncate">{repo.fullName}</span>
{repo.isPrivate ? (
<Lock className="h-3 w-3 shrink-0 text-muted-foreground" />
) : (
<Globe className="h-3 w-3 shrink-0 text-muted-foreground" />
)}
</div>
{repo.description && (
<p className="text-xs text-muted-foreground truncate">{repo.description}</p>
)}
{cloneError && (
<p className="text-xs text-destructive">{cloneError}</p>
)}
</div>

<div className="shrink-0">
{alreadyCloned || status === 'done' ? (
<span className="flex items-center gap-1 text-xs text-green-600">
<CheckCircle2 className="h-3.5 w-3.5" />
Cloned
</span>
) : status === 'cloning' ? (
<Button variant="outline" size="sm" disabled className="h-7 text-xs">
<Loader2 className="mr-1 h-3 w-3 animate-spin" />
Cloning...
</Button>
) : (
<Button
variant="outline"
size="sm"
onClick={() => handleCloneRepo(repo)}
className="h-7 text-xs"
>
<Download className="mr-1 h-3 w-3" />
Clone
</Button>
)}
</div>
</div>
);
})}
</div>
</>
)}
</div>

{selectedBranch && (
<p className="text-xs text-muted-foreground pl-6">
{t('settings:projectSections.github.defaultBranch.selectedBranchHelp', { branch: selectedBranch })}
</p>
)}
</div>
</>
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Hardcoded strings violate i18n guidelines.

The customer-specific clone section contains multiple hardcoded English strings that break localization. These should use translation keys from the settings:github namespace that were added in this PR.

Affected strings:

  • Line 442: "Cloned Repositories"t('settings:github.clonedRepositories')
  • Line 464: "Clone Repositories"t('settings:github.cloneRepositories')
  • Line 477: 'Refresh' / 'Load Repos't('settings:github.refresh') / t('settings:github.loadRepos')
  • Line 495: "Search repositories..."t('settings:github.searchRepos')
  • Line 540: "Cloned"t('settings:github.cloned')
  • Line 545: "Cloning..."t('settings:github.cloning')
  • Line 555: "Clone"t('settings:github.clone')

As per coding guidelines: "All frontend user-facing text must use react-i18next translation keys."

🌐 Proposed fix for i18n compliance
               <Label className="text-sm font-medium text-foreground flex items-center gap-2">
                 <FolderGit2 className="h-4 w-4" />
-                Cloned Repositories
+                {t('settings:github.clonedRepositories')}
               </Label>
               <Label className="text-sm font-medium text-foreground flex items-center gap-2">
                 <Download className="h-4 w-4" />
-                Clone Repositories
+                {t('settings:github.cloneRepositories')}
               </Label>
-                    {customerRepos.length > 0 ? 'Refresh' : 'Load Repos'}
+                    {customerRepos.length > 0 ? t('settings:github.refresh') : t('settings:github.loadRepos')}
                     <Input
                       type="text"
                       value={customerRepoSearch}
                       onChange={(e) => setCustomerRepoSearch(e.target.value)}
-                      placeholder="Search repositories..."
+                      placeholder={t('settings:github.searchRepos')}
                       className="h-8 text-xs"
                     />
                             {alreadyCloned || status === 'done' ? (
                               <span className="flex items-center gap-1 text-xs text-green-600">
                                 <CheckCircle2 className="h-3.5 w-3.5" />
-                                Cloned
+                                {t('settings:github.cloned')}
                               </span>
                             ) : status === 'cloning' ? (
                               <Button variant="outline" size="sm" disabled className="h-7 text-xs">
                                 <Loader2 className="mr-1 h-3 w-3 animate-spin" />
-                                Cloning...
+                                {t('settings:github.cloning')}
                               </Button>
                             ) : (
                               <Button
                                 variant="outline"
                                 size="sm"
                                 onClick={() => handleCloneRepo(repo)}
                                 className="h-7 text-xs"
                               >
                                 <Download className="mr-1 h-3 w-3" />
-                                Clone
+                                {t('settings:github.clone')}
                               </Button>
📝 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
{/* Customer-specific: Clone Repositories */}
{projectType === 'customer' && envConfig.githubToken && (
<>
<Separator />
{/* Default Branch Selector */}
{projectPath && (
<div className="space-y-2">
<div className="flex items-center justify-between">
<div className="space-y-0.5">
<div className="flex items-center gap-2">
<GitBranch className="h-4 w-4 text-info" />
<Label className="text-sm font-medium text-foreground">
{t('settings:projectSections.github.defaultBranch.label')}
</Label>
{/* Already cloned repos */}
{customerChildProjects.length > 0 && (
<div className="space-y-2">
<Label className="text-sm font-medium text-foreground flex items-center gap-2">
<FolderGit2 className="h-4 w-4" />
Cloned Repositories
</Label>
<div className="space-y-1.5">
{customerChildProjects.map((child) => (
<div
key={child.id}
className="flex items-center gap-2 rounded-md border border-border bg-muted/50 px-3 py-2 text-sm"
>
<CheckCircle2 className="h-4 w-4 text-green-500 shrink-0" />
<span className="truncate font-medium">{child.name}</span>
<span className="text-xs text-muted-foreground truncate ml-auto">{child.path}</span>
</div>
))}
</div>
<p className="text-xs text-muted-foreground pl-6">
{t('settings:projectSections.github.defaultBranch.description')}
</p>
</div>
<Button
variant="ghost"
size="sm"
onClick={fetchBranches}
disabled={isLoadingBranches}
className="h-7 px-2"
>
<RefreshCw className={`h-3 w-3 ${isLoadingBranches ? 'animate-spin' : ''}`} />
</Button>
</div>
)}
{branchesError && (
<div className="flex items-center gap-2 text-xs text-destructive pl-6">
<AlertCircle className="h-3 w-3" />
{branchesError}
{/* Clone new repos */}
<div className="space-y-3">
<div className="flex items-center justify-between">
<Label className="text-sm font-medium text-foreground flex items-center gap-2">
<Download className="h-4 w-4" />
Clone Repositories
</Label>
<Button
variant="outline"
size="sm"
onClick={loadCustomerRepos}
disabled={isLoadingCustomerRepos}
>
{isLoadingCustomerRepos ? (
<Loader2 className="mr-1.5 h-3.5 w-3.5 animate-spin" />
) : (
<RefreshCw className="mr-1.5 h-3.5 w-3.5" />
)}
{customerRepos.length > 0 ? 'Refresh' : 'Load Repos'}
</Button>
</div>
)}
<div className="pl-6">
<Combobox
options={branchOptions}
value={selectedBranch}
onValueChange={handleBranchChange}
placeholder={t('settings:projectSections.github.defaultBranch.autoDetect')}
searchPlaceholder={t('settings:projectSections.github.defaultBranch.searchPlaceholder')}
emptyMessage={t('settings:projectSections.github.defaultBranch.noBranchesFound')}
disabled={isLoadingBranches}
className="w-full"
/>
{customerReposError && (
<div className="flex items-center gap-2 text-xs text-destructive">
<AlertCircle className="h-3 w-3" />
{customerReposError}
</div>
)}
{customerRepos.length > 0 && (
<>
{/* Search */}
<Input
type="text"
value={customerRepoSearch}
onChange={(e) => setCustomerRepoSearch(e.target.value)}
placeholder="Search repositories..."
className="h-8 text-xs"
/>
{/* Repo list */}
<div className="space-y-1.5 max-h-[300px] overflow-y-auto">
{filteredCustomerRepos.map((repo) => {
const status = cloneStatuses[repo.fullName] || 'idle';
const alreadyCloned = customerChildProjects.some(
p => p.name === repo.fullName.split('/').pop()
);
const cloneError = cloneErrors[repo.fullName];
return (
<div
key={repo.fullName}
className={cn(
'flex items-center gap-2 rounded-md border px-3 py-2 text-sm',
status === 'done' || alreadyCloned
? 'border-green-500/30 bg-green-500/5'
: 'border-border'
)}
>
<Github className="h-4 w-4 shrink-0 text-muted-foreground" />
<div className="flex-1 min-w-0">
<div className="flex items-center gap-1.5">
<span className="text-xs font-medium truncate">{repo.fullName}</span>
{repo.isPrivate ? (
<Lock className="h-3 w-3 shrink-0 text-muted-foreground" />
) : (
<Globe className="h-3 w-3 shrink-0 text-muted-foreground" />
)}
</div>
{repo.description && (
<p className="text-xs text-muted-foreground truncate">{repo.description}</p>
)}
{cloneError && (
<p className="text-xs text-destructive">{cloneError}</p>
)}
</div>
<div className="shrink-0">
{alreadyCloned || status === 'done' ? (
<span className="flex items-center gap-1 text-xs text-green-600">
<CheckCircle2 className="h-3.5 w-3.5" />
Cloned
</span>
) : status === 'cloning' ? (
<Button variant="outline" size="sm" disabled className="h-7 text-xs">
<Loader2 className="mr-1 h-3 w-3 animate-spin" />
Cloning...
</Button>
) : (
<Button
variant="outline"
size="sm"
onClick={() => handleCloneRepo(repo)}
className="h-7 text-xs"
>
<Download className="mr-1 h-3 w-3" />
Clone
</Button>
)}
</div>
</div>
);
})}
</div>
</>
)}
</div>
{selectedBranch && (
<p className="text-xs text-muted-foreground pl-6">
{t('settings:projectSections.github.defaultBranch.selectedBranchHelp', { branch: selectedBranch })}
</p>
)}
</div>
</>
{/* Customer-specific: Clone Repositories */}
{projectType === 'customer' && envConfig.githubToken && (
<>
<Separator />
{/* Already cloned repos */}
{customerChildProjects.length > 0 && (
<div className="space-y-2">
<Label className="text-sm font-medium text-foreground flex items-center gap-2">
<FolderGit2 className="h-4 w-4" />
{t('settings:github.clonedRepositories')}
</Label>
<div className="space-y-1.5">
{customerChildProjects.map((child) => (
<div
key={child.id}
className="flex items-center gap-2 rounded-md border border-border bg-muted/50 px-3 py-2 text-sm"
>
<CheckCircle2 className="h-4 w-4 text-green-500 shrink-0" />
<span className="truncate font-medium">{child.name}</span>
<span className="text-xs text-muted-foreground truncate ml-auto">{child.path}</span>
</div>
))}
</div>
</div>
)}
{/* Clone new repos */}
<div className="space-y-3">
<div className="flex items-center justify-between">
<Label className="text-sm font-medium text-foreground flex items-center gap-2">
<Download className="h-4 w-4" />
{t('settings:github.cloneRepositories')}
</Label>
<Button
variant="outline"
size="sm"
onClick={loadCustomerRepos}
disabled={isLoadingCustomerRepos}
>
{isLoadingCustomerRepos ? (
<Loader2 className="mr-1.5 h-3.5 w-3.5 animate-spin" />
) : (
<RefreshCw className="mr-1.5 h-3.5 w-3.5" />
)}
{customerRepos.length > 0 ? t('settings:github.refresh') : t('settings:github.loadRepos')}
</Button>
</div>
{customerReposError && (
<div className="flex items-center gap-2 text-xs text-destructive">
<AlertCircle className="h-3 w-3" />
{customerReposError}
</div>
)}
{customerRepos.length > 0 && (
<>
{/* Search */}
<Input
type="text"
value={customerRepoSearch}
onChange={(e) => setCustomerRepoSearch(e.target.value)}
placeholder={t('settings:github.searchRepos')}
className="h-8 text-xs"
/>
{/* Repo list */}
<div className="space-y-1.5 max-h-[300px] overflow-y-auto">
{filteredCustomerRepos.map((repo) => {
const status = cloneStatuses[repo.fullName] || 'idle';
const alreadyCloned = customerChildProjects.some(
p => p.name === repo.fullName.split('/').pop()
);
const cloneError = cloneErrors[repo.fullName];
return (
<div
key={repo.fullName}
className={cn(
'flex items-center gap-2 rounded-md border px-3 py-2 text-sm',
status === 'done' || alreadyCloned
? 'border-green-500/30 bg-green-500/5'
: 'border-border'
)}
>
<Github className="h-4 w-4 shrink-0 text-muted-foreground" />
<div className="flex-1 min-w-0">
<div className="flex items-center gap-1.5">
<span className="text-xs font-medium truncate">{repo.fullName}</span>
{repo.isPrivate ? (
<Lock className="h-3 w-3 shrink-0 text-muted-foreground" />
) : (
<Globe className="h-3 w-3 shrink-0 text-muted-foreground" />
)}
</div>
{repo.description && (
<p className="text-xs text-muted-foreground truncate">{repo.description}</p>
)}
{cloneError && (
<p className="text-xs text-destructive">{cloneError}</p>
)}
</div>
<div className="shrink-0">
{alreadyCloned || status === 'done' ? (
<span className="flex items-center gap-1 text-xs text-green-600">
<CheckCircle2 className="h-3.5 w-3.5" />
{t('settings:github.cloned')}
</span>
) : status === 'cloning' ? (
<Button variant="outline" size="sm" disabled className="h-7 text-xs">
<Loader2 className="mr-1 h-3 w-3 animate-spin" />
{t('settings:github.cloning')}
</Button>
) : (
<Button
variant="outline"
size="sm"
onClick={() => handleCloneRepo(repo)}
className="h-7 text-xs"
>
<Download className="mr-1 h-3 w-3" />
{t('settings:github.clone')}
</Button>
)}
</div>
</div>
);
})}
</div>
</>
)}
</div>
</>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/desktop/src/renderer/components/settings/integrations/GitHubIntegration.tsx`
around lines 432 - 566, Replace the hardcoded English UI strings in the
customer-specific clone section of GitHubIntegration (the JSX that renders
Cloned Repositories, Clone Repositories, button labels, search placeholder, and
per-repo status labels) with the corresponding react-i18next keys from the
settings:github namespace (e.g., use t('settings:github.clonedRepositories'),
t('settings:github.cloneRepositories'), t('settings:github.refresh') /
t('settings:github.loadRepos'), t('settings:github.searchRepos'),
t('settings:github.cloned'), t('settings:github.cloning'),
t('settings:github.clone')). Ensure the component uses the useTranslation hook
(t) already present in the file and update strings in the JSX blocks that
reference customerChildProjects, loadCustomerRepos, customerRepoSearch,
filteredCustomerRepos, cloneStatuses, cloneErrors, and handleCloneRepo so all
user-facing text uses t(...).

Comment on lines +140 to +146
if ((selectedProject as Project & { type?: string }).type === 'customer') return selectedProject;
const normalizedSelected = normalizePath(selectedProject.path);
const parentCustomer = projects.find(
p => (p as Project & { type?: string }).type === 'customer' && normalizedSelected.startsWith(normalizePath(p.path) + '/')
);
return parentCustomer ?? null;
}, [selectedProject, projects]);
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check if Project type already has a 'type' field
ast-grep --pattern 'interface Project {
  $$$
}'

# Also check type definition files
rg -n "type Project\s*=" --type ts
rg -n "interface Project" --type ts -A 10

Repository: AndyMik90/Aperant

Length of output: 19698


🏁 Script executed:

# Search for all type assertions related to Project & { type?: string } in Sidebar.tsx
rg -n "Project.*type\?" apps/desktop/src/renderer/components/Sidebar.tsx -A 2 -B 2

# Also check TypeScript configuration
cat apps/desktop/tsconfig.json | head -30

Repository: AndyMik90/Aperant

Length of output: 1917


Remove unnecessary type assertions for the type field.

The Project interface already includes type?: 'project' | 'customer' (defined in apps/desktop/src/shared/types/project.ts), making the repeated assertions (selectedProject as Project & { type?: string }) on lines 140, 143, 258, and 451 redundant. Remove these assertions and access the field directly: selectedProject.type === 'customer'. Additionally, the assertions widen the type to string, losing type safety—use the existing type definition instead.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/desktop/src/renderer/components/Sidebar.tsx` around lines 140 - 146,
Remove the redundant broad type assertions around selectedProject and project
elements; directly use the existing Project.type optional union. In the memo
that computes parentCustomer (references: selectedProject, projects,
normalizePath, parentCustomer), replace occurrences like (selectedProject as
Project & { type?: string }).type with selectedProject.type and similarly remove
casts on p in the projects.find callback so you compare p.type === 'customer'
and rely on TypeScript's Project definition; ensure null/undefined checks remain
for optional type where needed.

Comment on lines +144 to +145
checkGlobalMcpHealth: async (server: any) => ({
success: true,
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify remaining explicit `any` in this mock file
rg -nP '\b:\s*any\b' apps/desktop/src/renderer/lib/browser-mock.ts

Repository: AndyMik90/Aperant

Length of output: 114


🏁 Script executed:

# Find ElectronAPI interface definition
fd -t f -e ts -e tsx | xargs rg -l "interface ElectronAPI|type ElectronAPI" | head -5

Repository: AndyMik90/Aperant

Length of output: 135


🏁 Script executed:

# Check tsconfig.json for strict mode
cat apps/desktop/tsconfig.json 2>/dev/null || cat tsconfig.json 2>/dev/null || find . -name "tsconfig.json" -type f | head -3

Repository: AndyMik90/Aperant

Length of output: 876


🏁 Script executed:

# View context around line 144 in browser-mock.ts
sed -n '140,160p' apps/desktop/src/renderer/lib/browser-mock.ts

Repository: AndyMik90/Aperant

Length of output: 651


🏁 Script executed:

# Check the ElectronAPI interface definition for checkGlobalMcpHealth
rg -A 3 "checkGlobalMcpHealth" apps/desktop/src/preload/api/index.ts apps/desktop/src/shared/types/ipc.ts

Repository: AndyMik90/Aperant

Length of output: 441


🏁 Script executed:

# Also view the full ElectronAPI type definition to understand server parameter type
rg -B 5 "checkGlobalMcpHealth" apps/desktop/src/shared/types/ipc.ts | head -20

Repository: AndyMik90/Aperant

Length of output: 314


🏁 Script executed:

# Check CustomMcpServer definition
rg -B 2 -A 5 "type CustomMcpServer|interface CustomMcpServer" apps/desktop/src/shared/types/

Repository: AndyMik90/Aperant

Length of output: 662


Use the correct type for server parameter in mock.

checkGlobalMcpHealth should use CustomMcpServer type to match the ElectronAPI interface signature:

-  checkGlobalMcpHealth: async (server: any) => ({
+  checkGlobalMcpHealth: async (server: CustomMcpServer) => ({

The interface in apps/desktop/src/shared/types/ipc.ts defines (server: CustomMcpServer), and the mock function accesses server.id, confirming this is the correct type.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/desktop/src/renderer/lib/browser-mock.ts` around lines 144 - 145, The
mock function checkGlobalMcpHealth currently types its parameter as any; update
its signature to use the CustomMcpServer type so it matches the ElectronAPI
interface and allows using server.id safely—locate checkGlobalMcpHealth in
browser-mock.ts and replace the server parameter type with CustomMcpServer
(importing or referencing CustomMcpServer from the shared types if necessary) to
align the mock with the interface.

isAutoProfile?: boolean; // True when using Auto (Optimized) profile
phaseModels?: PhaseModelConfig; // Per-phase model configuration
phaseThinking?: PhaseThinkingConfig; // Per-phase thinking configuration
phaseCustomAgents?: import('./settings').PhaseCustomAgentsConfig; // Per-phase custom agent IDs
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Consider using consistent import style.

The inline import('./settings').PhaseCustomAgentsConfig type works, but Line 5 already imports other types from ./settings. Adding PhaseCustomAgentsConfig to that import statement would be more consistent.

♻️ Suggested refactor

Update the import at Line 5:

-import type { ThinkingLevel, PhaseModelConfig, PhaseThinkingConfig } from './settings';
+import type { ThinkingLevel, PhaseModelConfig, PhaseThinkingConfig, PhaseCustomAgentsConfig } from './settings';

Then simplify Line 239:

-  phaseCustomAgents?: import('./settings').PhaseCustomAgentsConfig;  // Per-phase custom agent IDs
+  phaseCustomAgents?: PhaseCustomAgentsConfig;  // Per-phase custom agent IDs
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/desktop/src/shared/types/task.ts` at line 239, Replace the inline type
import used for phaseCustomAgents with a named import from ./settings to keep
import style consistent: add PhaseCustomAgentsConfig to the existing import list
from './settings' (the same import that currently brings in other types) and
then change the property declaration phaseCustomAgents?:
import('./settings').PhaseCustomAgentsConfig; to phaseCustomAgents?:
PhaseCustomAgentsConfig; so the file consistently uses the named type import.

setIsLoading(true);
setError(null);
try {
const result = await window.electronAPI.listGitHubUserRepos();

This comment was marked as outdated.

title: pr.title,
body: pr.body || '',
state: pr.state.toLowerCase(),
author: { login: pr.user.login },
Copy link

Choose a reason for hiding this comment

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

Bug: The code accesses pr.user.login without a null check, causing a TypeError and failing the entire PR fetch if any PR is from a deleted user.
Severity: HIGH

Suggested Fix

Use optional chaining and a nullish coalescing operator to safely access the author's login, similar to other parts of the codebase. Change pr.user.login to pr.user?.login ?? 'unknown'. This will prevent the TypeError and ensure the operation can complete successfully.

Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent.
Verify if this is a real issue. If it is, propose a fix; if not, explain why it's not
valid.

Location: apps/desktop/src/main/ipc-handlers/github/customer-github-handlers.ts#L375

Potential issue: In the `getMultiRepoPRs` handler, the code transforms pull request data
by accessing `pr.user.login` without a null check. The GitHub API can return a `null`
value for the `user` object if the author's account has been deleted. This will throw a
`TypeError`, which is caught but causes the entire multi-repo PR fetch operation to fail
for the customer, even if only one PR has a null author. Other parts of the codebase
already handle this case safely.

Did we get this right? 👍 / 👎 to inform future reviews.

Comment on lines +83 to +87
// Fallback: try getGitHubConfig which also checks gh CLI
if (!token) {
const fallbackConfig = getGitHubConfig(customer);
token = fallbackConfig?.token;
}
Copy link

Choose a reason for hiding this comment

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

Bug: A race condition during new customer project creation causes GitHub operations to fail on first use because autoBuildPath is not yet available when getCustomerGitHubConfig is called.
Severity: HIGH

Suggested Fix

Modify the PROJECT_ADD IPC handler to accept and persist the type: 'customer' parameter when adding a project. This ensures that the project object is correctly configured from the start, or alternatively, make getCustomerGitHubConfig wait for the .auto-claude/ directory to exist before proceeding.

Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent.
Verify if this is a real issue. If it is, propose a fix; if not, explain why it's not
valid.

Location: apps/desktop/src/main/ipc-handlers/github/customer-github-handlers.ts#L83-L87

Potential issue: When a new customer project is created, the `addProject` IPC handler
does not persist the project's `type`. A separate, asynchronous call to
`initializeCustomerProject` is responsible for creating the `.auto-claude/` directory
and setting the `autoBuildPath`. This creates a race condition where GitHub-related IPC
handlers like `getMultiRepoIssues` can be called before `autoBuildPath` is set. The
`getCustomerGitHubConfig` function then returns `null`, causing all multi-repo GitHub
operations to fail until the application is restarted.

Did we get this right? 👍 / 👎 to inform future reviews.

Adds a compact status banner in TaskCard that appears when an agent is
actively running. Shows:
- Current subtask being analyzed (with phase-colored pulsing dot)
- Next pending subtask in the queue
- Live elapsed timer (MM:SS, auto-updating)
- Activity summary: completed/in-progress counts from last 30 minutes
- Phase transition message when available

The banner renders only during active execution phases and disappears
when idle, complete, or failed. Uses motion/react for smooth entrance
animation and i18n for all text (en + fr translations included).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Comment on lines +59 to +65
const result = await window.electronAPI.getClaudeAgents();
if (result.success && result.data) {
setAgentsInfo(result.data);
}
} catch {
// Silently fail - custom agents are optional
}
Copy link

Choose a reason for hiding this comment

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

Bug: The getClaudeAgents function is called in the renderer but is not exposed on the electronAPI in the preload script, causing the call to fail silently.
Severity: CRITICAL

Suggested Fix

Expose the getClaudeAgents function in the preload API. In a relevant preload API module, create a function that uses invokeIpc to call the CLAUDE_AGENTS_GET IPC channel and add it to the electronAPI interface.

Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent.
Verify if this is a real issue. If it is, propose a fix; if not, explain why it's not
valid.

Location: apps/desktop/src/renderer/components/settings/AgentProfileSettings.tsx#L59-L65

Potential issue: The `AgentProfileSettings.tsx` component attempts to call
`window.electronAPI.getClaudeAgents()` to fetch custom agents. However, this function is
not exposed on the `electronAPI` object in the preload script. While an IPC handler for
`CLAUDE_AGENTS_GET` exists in the main process, the necessary bridge in the preload API
to connect the renderer's call to the main process handler is missing. This results in a
`TypeError` at runtime, which is caught and suppressed, causing the feature to fail
silently. Consequently, the custom agents catalog will always appear empty to the user.

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

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/renderer/components/TaskCard.tsx (1)

101-102: ⚠️ Potential issue | 🟡 Minor

Consider adding currentSubtask to memo comparator.

The AgentAnalysisBanner displays executionProgress?.currentSubtask, but taskCardPropsAreEqual only compares phase and phaseProgress. If currentSubtask changes independently, the card won't re-render and the banner will show stale data.

🛠️ Suggested fix
     prevTask.executionProgress?.phase === nextTask.executionProgress?.phase &&
     prevTask.executionProgress?.phaseProgress === nextTask.executionProgress?.phaseProgress &&
+    prevTask.executionProgress?.currentSubtask === nextTask.executionProgress?.currentSubtask &&
     prevTask.subtasks.length === nextTask.subtasks.length &&
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/desktop/src/renderer/components/TaskCard.tsx` around lines 101 - 102,
taskCardPropsAreEqual's memo comparator only checks executionProgress.phase and
phaseProgress, so AgentAnalysisBanner's executionProgress?.currentSubtask can
change without triggering a re-render; update the comparator function
taskCardPropsAreEqual to also compare prevTask.executionProgress?.currentSubtask
=== nextTask.executionProgress?.currentSubtask (handling undefined/null safely)
so the card re-renders when currentSubtask changes.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/desktop/src/renderer/components/AgentAnalysisBanner.tsx`:
- Around line 124-128: The activitySummary computed in the useMemo (const
activitySummary = useMemo(...)) ignores subtasks with status 'failed' even
though SubtaskStatus includes 'failed'; update the calculation to include a
failedCount (e.g., count subtasks where s.status === 'failed') and return it
from activitySummary, then update any UI that consumes activitySummary (the
AgentAnalysisBanner component) to display or appropriately group failedCount (or
merge it with completedCount if that behavior is desired).
- Around line 4-5: Replace the relative imports in AgentAnalysisBanner.tsx with
the configured path aliases: change the import of cn from '../lib/utils' to use
'@lib/utils' and change the type imports (ExecutionPhase, ExecutionProgress,
Subtask) from '../../shared/types' to '@shared/types' so the file uses the
tsconfig path aliases; update any other similar relative imports in this file to
their `@lib/`* or `@shared/`* equivalents to match project guidelines.
- Around line 123-129: The activitySummary currently counts all subtasks but the
UI label (I18N.last30min) implies a 30-minute window; update the useMemo in
activitySummary to filter subtasks by a timestamp (e.g., subtask.statusUpdatedAt
or subtask.updatedAt) within the last 30 minutes before computing
completedCount, inProgressCount, and pendingCount, using Date.now() - 30*60*1000
for the cutoff; if subtasks lack a timestamp field, add/propagate a
statusUpdatedAt (or updatedAt) to each subtask when its status changes and then
use that field in the filter, leaving I18N.last30min as-is.

---

Outside diff comments:
In `@apps/desktop/src/renderer/components/TaskCard.tsx`:
- Around line 101-102: taskCardPropsAreEqual's memo comparator only checks
executionProgress.phase and phaseProgress, so AgentAnalysisBanner's
executionProgress?.currentSubtask can change without triggering a re-render;
update the comparator function taskCardPropsAreEqual to also compare
prevTask.executionProgress?.currentSubtask ===
nextTask.executionProgress?.currentSubtask (handling undefined/null safely) so
the card re-renders when currentSubtask changes.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 5ed5315c-8c63-4160-b4b0-c3c6952e253a

📥 Commits

Reviewing files that changed from the base of the PR and between 85d1d26 and 135276c.

📒 Files selected for processing (4)
  • apps/desktop/src/renderer/components/AgentAnalysisBanner.tsx
  • apps/desktop/src/renderer/components/TaskCard.tsx
  • apps/desktop/src/shared/i18n/locales/en/tasks.json
  • apps/desktop/src/shared/i18n/locales/fr/tasks.json

Comment on lines +4 to +5
import { cn } from '../lib/utils';
import type { ExecutionPhase, ExecutionProgress, Subtask } from '../../shared/types';
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Use path aliases for imports.

The coding guidelines specify using path aliases defined in tsconfig.json for imports. Consider using @lib/* and @shared/* aliases instead of relative paths.

♻️ Suggested refactor
-import { cn } from '../lib/utils';
-import type { ExecutionPhase, ExecutionProgress, Subtask } from '../../shared/types';
+import { cn } from '@lib/utils';
+import type { ExecutionPhase, ExecutionProgress, Subtask } from '@shared/types';
📝 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
import { cn } from '../lib/utils';
import type { ExecutionPhase, ExecutionProgress, Subtask } from '../../shared/types';
import { cn } from '@lib/utils';
import type { ExecutionPhase, ExecutionProgress, Subtask } from '@shared/types';
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/desktop/src/renderer/components/AgentAnalysisBanner.tsx` around lines 4
- 5, Replace the relative imports in AgentAnalysisBanner.tsx with the configured
path aliases: change the import of cn from '../lib/utils' to use '@lib/utils'
and change the type imports (ExecutionPhase, ExecutionProgress, Subtask) from
'../../shared/types' to '@shared/types' so the file uses the tsconfig path
aliases; update any other similar relative imports in this file to their `@lib/`*
or `@shared/`* equivalents to match project guidelines.

Comment on lines +123 to +129
// Activity summary for the last 30 minutes
const activitySummary = useMemo(() => {
const completedCount = subtasks.filter((s) => s.status === 'completed').length;
const inProgressCount = subtasks.filter((s) => s.status === 'in_progress').length;
const pendingCount = subtasks.filter((s) => s.status === 'pending').length;
return { completedCount, inProgressCount, pendingCount };
}, [subtasks]);
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Activity summary label does not match implementation.

The UI displays "Last 30min" (line 195 via I18N.last30min), but activitySummary counts all subtasks regardless of when they changed status. This is misleading to users who expect time-filtered metrics.

Either:

  1. Remove the "Last 30min" qualifier and just show totals, or
  2. Add timestamp tracking to subtasks and filter by the last 30 minutes
🐛 Option 1: Remove time qualifier from label

Update i18n keys to remove the time reference:

// In en/tasks.json and fr/tasks.json
-"last30min": "Last 30min",
+"activity": "Activity",

And update the I18N constant:

 const I18N = {
   // ...
-  last30min: 'analysisBanner.last30min',
+  activity: 'analysisBanner.activity',
   // ...
 } as const;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/desktop/src/renderer/components/AgentAnalysisBanner.tsx` around lines
123 - 129, The activitySummary currently counts all subtasks but the UI label
(I18N.last30min) implies a 30-minute window; update the useMemo in
activitySummary to filter subtasks by a timestamp (e.g., subtask.statusUpdatedAt
or subtask.updatedAt) within the last 30 minutes before computing
completedCount, inProgressCount, and pendingCount, using Date.now() - 30*60*1000
for the cutoff; if subtasks lack a timestamp field, add/propagate a
statusUpdatedAt (or updatedAt) to each subtask when its status changes and then
use that field in the filter, leaving I18N.last30min as-is.

Comment on lines +124 to +128
const activitySummary = useMemo(() => {
const completedCount = subtasks.filter((s) => s.status === 'completed').length;
const inProgressCount = subtasks.filter((s) => s.status === 'in_progress').length;
const pendingCount = subtasks.filter((s) => s.status === 'pending').length;
return { completedCount, inProgressCount, pendingCount };
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Consider handling 'failed' subtask status.

Per apps/desktop/src/shared/types/task.ts, SubtaskStatus includes 'failed', but it's not counted in activitySummary. Failed subtasks will be invisible in the activity display.

If intentional, this is fine. Otherwise, consider adding a failed count or grouping failed with completed.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/desktop/src/renderer/components/AgentAnalysisBanner.tsx` around lines
124 - 128, The activitySummary computed in the useMemo (const activitySummary =
useMemo(...)) ignores subtasks with status 'failed' even though SubtaskStatus
includes 'failed'; update the calculation to include a failedCount (e.g., count
subtasks where s.status === 'failed') and return it from activitySummary, then
update any UI that consumes activitySummary (the AgentAnalysisBanner component)
to display or appropriately group failedCount (or merge it with completedCount
if that behavior is desired).

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.

4 participants