Skip to content

feat(integrations): pear-side integrations feature (manager + catalog + Nango logos)#9

Merged
khaliqgant merged 2 commits into
mainfrom
ricky/wave-pear-cloud-agents/02-integrations
May 21, 2026
Merged

feat(integrations): pear-side integrations feature (manager + catalog + Nango logos)#9
khaliqgant merged 2 commits into
mainfrom
ricky/wave-pear-cloud-agents/02-integrations

Conversation

@khaliqgant

Copy link
Copy Markdown
Member

Spec 02 scaffolding plus the catalog/logo work done this session. Builds on #7 (shared scaffold).

  • integrations.ts: IntegrationsManager. listCatalog filters to ACTIVE_PROVIDERS (the non-deprecated relayfile providers from cloud/packages/web/lib/integrations/providers.ts). authHeaders is async and uses resolveCloudAuth. loadStaticCatalog is a static import — the previous dynamic variable import await import(CATALOG_MODULE_PATH) couldn't be resolved by the bundler, so the static fallback was empty at runtime.
  • integrations.catalog.ts: 36 generated adapters with iconUrl Nango logos.
  • scripts/build-integrations-catalog.mjs: emits iconUrl with the NANGO_LOGO_SLUG overrides (gmail→google-mail, teams→microsoft-teams, x→twitter).
  • AccountSettings.tsx: <IntegrationLogo> with onError fallback to the plug icon; used in both the CATALOG grid and the CONNECTED list.

Remaining gaps (scope pickers, per-project visibility section, ../relayfile-cloud workspace-integrations route) tracked in slim spec specs/02-integrations.md from #5. Connect-flow rework is spec 05.

🤖 Generated with Claude Code

@coderabbitai

coderabbitai Bot commented May 21, 2026

Copy link
Copy Markdown

Warning

Rate limit exceeded

@khaliqgant has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 13 minutes and 7 seconds before requesting another review.

You’ve run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Free

Run ID: ca09ac92-9936-4ea0-a2d1-078bee57c1f3

📥 Commits

Reviewing files that changed from the base of the PR and between d66ef8b and 540a06a.

📒 Files selected for processing (6)
  • scripts/README.md
  • scripts/build-integrations-catalog.mjs
  • src/main/integrations.catalog.ts
  • src/main/integrations.ts
  • src/main/integrations.types.ts
  • src/renderer/src/components/settings/AccountSettings.tsx

Note

🎁 Summarized by CodeRabbit Free

Your organization is on the Free plan. CodeRabbit will generate a high-level summary and a walkthrough for each pull request. For a comprehensive line-by-line review, please upgrade your subscription to CodeRabbit Pro by visiting https://app.coderabbit.ai/login.

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

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Devin Review found 1 potential issue.

View 6 additional findings in Devin Review.

Open in Devin Review

Comment thread src/main/integrations.ts
Comment on lines +821 to +834
private async persistIntegration(projectId: string, integration: ConnectedIntegration): Promise<void> {
const data = loadStore()
const project = data.projects.find((entry) => entry.id === projectId)
if (!project) throw new Error(`Project not found: ${projectId}`)

const displayName = await this.displayNameForProvider(integration.provider)
const stored = toStoredIntegration(integration, displayName)
project.integrations = project.integrations.filter((entry) => {
const current = normalizeConnectedIntegration(entry)
return current?.integrationId !== integration.integrationId
})
project.integrations.push(stored)
saveStore(data)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔴 Store race condition in persistIntegration causes data loss when catalog cache is stale

The persistIntegration method reads the store with loadStore() at line 822, then awaits this.displayNameForProvider(integration.provider) at line 826 (which calls listCatalog() and may perform a network fetch when the 5-minute catalog cache expires), then writes the store with saveStore(data) at line 833. During the async gap, any other IPC handler that modifies and saves the store (e.g., project:update, project:add-channel, project:add-root) will have its changes silently overwritten by the stale data object. This contrasts with removePersistedIntegration at src/main/integrations.ts:836 which is effectively synchronous (load → modify → save without yielding).

Prompt for agents
In IntegrationsManager.persistIntegration (src/main/integrations.ts lines 821-834), the method calls loadStore(), then awaits displayNameForProvider (which may trigger a network fetch when the catalog cache is stale), then calls saveStore(data). During the async gap from the await, other IPC handlers can run and modify the store, and those changes will be lost when the stale data is saved.

To fix this, move the async displayNameForProvider call BEFORE loadStore(), or re-read the store after the await. The simplest fix:

1. Call `const displayName = await this.displayNameForProvider(integration.provider)` first
2. Then do the synchronous loadStore() → modify → saveStore() sequence without any async gaps in between

This matches the pattern used by removePersistedIntegration which correctly keeps the load-modify-save sequence synchronous.
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

@khaliqgant khaliqgant changed the base branch from wave/pear-shared-scaffold to main May 21, 2026 14:08
khaliqgant and others added 2 commits May 21, 2026 16:09
… + Nango logos)

Spec 02 scaffolding plus the catalog/logo work done this session:
- src/main/integrations.ts: IntegrationsManager. listCatalog filters to
  ACTIVE_PROVIDERS (the non-deprecated relayfile providers in
  cloud/packages/web/lib/integrations/providers.ts). authHeaders is async and
  routes through resolveCloudAuth. loadStaticCatalog is a static import (not a
  dynamic variable import, which the bundler couldn't resolve and which left
  the static fallback empty at runtime).
- src/main/integrations.types.ts: IntegrationAdapter + visibility types.
- src/main/integrations.catalog.ts: 36 generated adapters with iconUrl pointing
  at https://app.nango.dev/images/template-logos/{slug}.svg.
- scripts/build-integrations-catalog.mjs: regenerates the catalog from
  ../relayfile-adapters/packages. Adds NANGO_LOGO_SLUG override map
  (gmail→google-mail, teams→microsoft-teams, x→twitter) and emits iconUrl per
  entry.
- src/renderer/src/components/settings/AccountSettings.tsx: <IntegrationLogo>
  component with onError fallback to the generic plug icon (light tile makes
  the dark/monochrome SVGs legible), used in both the CATALOG grid and the
  CONNECTED list.

Builds on shared scaffolding (#7). Remaining gaps — scope-picker components +
per-project visibility section + ../relayfile-cloud workspace-integrations
route — are tracked in specs/02-integrations.md. The Connect-flow rework is
spec 05.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…nous

Resolve the async displayNameForProvider lookup BEFORE loadStore() so no
await sits between load and save. With the async gap in place, any concurrent
IPC handler (project:update, project:add-channel, etc.) that wrote to
projects.json during the awaited catalog refresh would have its changes
silently overwritten when we saved the stale data we loaded earlier.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@khaliqgant khaliqgant force-pushed the ricky/wave-pear-cloud-agents/02-integrations branch from 5de2c4e to 540a06a Compare May 21, 2026 14:09
@khaliqgant khaliqgant merged commit 93d4630 into main May 21, 2026
1 check passed
miyaontherelay added a commit that referenced this pull request Jun 8, 2026
Fix #9: track last-sent rows/cols and skip duplicate resizePty IPC. The
ResizeObserver fires on every dragged pixel; the cell grid only changes
at discrete steps.

Fix #12: refuse a second concurrent mount of the same runtime into a
different container. Currently chunkAgents doesn't trigger this, but
silently reparenting would tear xterm out from under the original owner.

Fix #15: post-font-settle metrics may differ from the pre-settle ones
the predictor was built with. Call predictiveEcho.onResize after the
refit so column wraps line up with the real grid.

Also adds refreshOnShow() and setInputSrttGetter() to the runtime
interface in preparation for use-terminal wiring.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant