Repository navigation
improvement(platform): consolidate integrations and credential management - #9725
emir-karabeg wants to merge 2 commits into
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
There was a problem hiding this comment.
7 issues found across 59 files
Confidence score: 3/5
search-modal.tsxcan send a viewer who isn’t a member of the host organization to its/o/...integrations route. Add the host-membership guard before building that URL.- Managed integrations are classified inconsistently in
integration-list.tsxandintegrations-page.tsx: they can look like member connections or appear underExplore apps, where members can’t connect them. Handleavailability === 'managed'consistently in both views. integrations.tsxdropscategoryandsearchwhen redirecting, so filtered or bookmarked workspace URLs open an unfiltered organization list. Preserve the supported query state in the redirect.search-field.module.csshas no visible keyboard-focus state for the quiet search field because the global input outline is removed. Add the shared:has(input:focus-visible)border treatment.
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/search-modal/search-modal.tsx">
<violation number="1" location="apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/search-modal/search-modal.tsx:163">
P1: This URL also needs the host-membership guard: a viewer can have a hosted workspace and `organizationSearch` enabled without belonging to the host organization, but `workspaceIntegrationsPath` then sends them to `/o/<host>/integrations`, whose layout denies non-members. Preserve the workspace integrations destination unless `host.viewer.isHostOrganizationMember` is true.</violation>
</file>
<file name="apps/sim/platform/app/(shell)/integrations/_components/fixtures/integrations-page/integrations-page.tsx">
<violation number="1" location="apps/sim/platform/app/(shell)/integrations/_components/fixtures/integrations-page/integrations-page.tsx:62">
P2: Managed integrations without a member account are rendered under `Explore apps`, even though they are organization-provided and cannot be connected by the member. Treat `availability === 'managed'` as connected here, matching the live summary adapter.</violation>
</file>
<file name="apps/sim/platform/design-system/components/search-field/search-field.module.css">
<violation number="1" location="apps/sim/platform/design-system/components/search-field/search-field.module.css:21">
P2: The quiet search field provides no visible keyboard-focus state. Global styles remove the input outline, so add the same `:has(input:focus-visible)` border treatment used by the shared field recipe.</violation>
</file>
<file name="apps/sim/platform/app/(shell)/integrations/_components/integration-list/integration-list.tsx">
<violation number="1" location="apps/sim/platform/app/(shell)/integrations/_components/integration-list/integration-list.tsx:33">
P2: Managed integrations are rendered as connected even when `accounts` is empty, so organization-managed providers such as GitLab appear in the wrong section and look like member connections. Set this from the presence of accounts only.</violation>
</file>
<file name="apps/sim/app/workspace/[workspaceId]/integrations/integrations.tsx">
<violation number="1" location="apps/sim/app/workspace/[workspaceId]/integrations/integrations.tsx:167">
P2: This redirect drops the workspace page’s `category` and `search` query state, so filtered or bookmarked integration URLs open the organization list unfiltered. Translate the supported state (at least `search` to the organization route’s `q`) before redirecting.</violation>
</file>
<file name="apps/sim/platform/app/(shell)/integrations/_components/integration-center/integration-center.tsx">
<violation number="1" location="apps/sim/platform/app/(shell)/integrations/_components/integration-center/integration-center.tsx:158">
P2: This writes the URL for every search keystroke, enqueueing a replace/navigation update while the user types. Use `useDebouncedSearchSetter` for `q` so the input remains responsive without issuing one URL update per key.</violation>
</file>
<file name="apps/sim/platform/app/(shell)/integrations/_components/integrations-page/integrations-page.tsx">
<violation number="1" location="apps/sim/platform/app/(shell)/integrations/_components/integrations-page/integrations-page.tsx:36">
P3: This shared heading promises team sharing on the live route, although live integrations render `IntegrationList` without the fixture-only sharing controls. Keep the live-safe copy here or make the heading mode-specific.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Turn on auto-fix | Re-trigger cubic
| const { chatEnabled } = useDeploymentShape() | ||
| const workspaceId = params.workspaceId as string | ||
| const host = useWorkspaceHostContext() | ||
| const integrationsHref = workspaceIntegrationsPath(workspaceId, host) |
There was a problem hiding this comment.
P1: This URL also needs the host-membership guard: a viewer can have a hosted workspace and organizationSearch enabled without belonging to the host organization, but workspaceIntegrationsPath then sends them to /o/<host>/integrations, whose layout denies non-members. Preserve the workspace integrations destination unless host.viewer.isHostOrganizationMember is true.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/search-modal/search-modal.tsx, line 163:
<comment>This URL also needs the host-membership guard: a viewer can have a hosted workspace and `organizationSearch` enabled without belonging to the host organization, but `workspaceIntegrationsPath` then sends them to `/o/<host>/integrations`, whose layout denies non-members. Preserve the workspace integrations destination unless `host.viewer.isHostOrganizationMember` is true.</comment>
<file context>
@@ -157,6 +159,8 @@ function SearchModalContent({
const { chatEnabled } = useDeploymentShape()
const workspaceId = params.workspaceId as string
+ const host = useWorkspaceHostContext()
+ const integrationsHref = workspaceIntegrationsPath(workspaceId, host)
const currentWorkflowId = params.workflowId as string | undefined
const inputRef = useRef<HTMLInputElement>(null)
</file context>
| const integrationsHref = workspaceIntegrationsPath(workspaceId, host) | |
| const integrationsHref = workspaceIntegrationsPath( | |
| workspaceId, | |
| host.viewer.isHostOrganizationMember ? host : null | |
| ) |
| description: integration.description, | ||
| category: appCategory(integration.providerId), | ||
| href: memberIntegrationHref(`/integrations/${integration.providerId}`, search), | ||
| connected: Boolean(integration.account), |
There was a problem hiding this comment.
P2: Managed integrations without a member account are rendered under Explore apps, even though they are organization-provided and cannot be connected by the member. Treat availability === 'managed' as connected here, matching the live summary adapter.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At apps/sim/platform/app/(shell)/integrations/_components/fixtures/integrations-page/integrations-page.tsx, line 62:
<comment>Managed integrations without a member account are rendered under `Explore apps`, even though they are organization-provided and cannot be connected by the member. Treat `availability === 'managed'` as connected here, matching the live summary adapter.</comment>
<file context>
@@ -42,15 +51,46 @@ export function IntegrationsPage({ data, section }: IntegrationsPageProps) {
+ description: integration.description,
+ category: appCategory(integration.providerId),
+ href: memberIntegrationHref(`/integrations/${integration.providerId}`, search),
+ connected: Boolean(integration.account),
+ status: memberStatus(integration),
+ searchable: integration.account?.label,
</file context>
| connected: Boolean(integration.account), | |
| connected: Boolean(integration.account) || integration.availability === 'managed', |
| .search[data-variant="quiet"] { | ||
| border-color: transparent; | ||
| background: transparent; | ||
| } | ||
| .search[data-variant="quiet"]:hover { | ||
| background: var(--ui-hover); | ||
| } |
There was a problem hiding this comment.
P2: The quiet search field provides no visible keyboard-focus state. Global styles remove the input outline, so add the same :has(input:focus-visible) border treatment used by the shared field recipe.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At apps/sim/platform/design-system/components/search-field/search-field.module.css, line 21:
<comment>The quiet search field provides no visible keyboard-focus state. Global styles remove the input outline, so add the same `:has(input:focus-visible)` border treatment used by the shared field recipe.</comment>
<file context>
@@ -18,6 +18,13 @@
.search[data-variant="sidebar"] {
border-color: var(--ui-divider);
}
+.search[data-variant="quiet"] {
+ border-color: transparent;
+ background: transparent;
</file context>
| .search[data-variant="quiet"] { | |
| border-color: transparent; | |
| background: transparent; | |
| } | |
| .search[data-variant="quiet"]:hover { | |
| background: var(--ui-hover); | |
| } | |
| .search[data-variant="quiet"] { | |
| border-color: transparent; | |
| background: transparent; | |
| } | |
| .search[data-variant="quiet"]:has(input:focus-visible) { | |
| border-color: var(--ui-secondary); | |
| } | |
| .search[data-variant="quiet"]:hover { | |
| background: var(--ui-hover); | |
| } |
| detail: item.description, | ||
| description: item.description, | ||
| category: appCategory(item.providerId), | ||
| connected: item.accounts.length > 0 || item.availability === 'managed', |
There was a problem hiding this comment.
P2: Managed integrations are rendered as connected even when accounts is empty, so organization-managed providers such as GitLab appear in the wrong section and look like member connections. Set this from the presence of accounts only.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At apps/sim/platform/app/(shell)/integrations/_components/integration-list/integration-list.tsx, line 33:
<comment>Managed integrations are rendered as connected even when `accounts` is empty, so organization-managed providers such as GitLab appear in the wrong section and look like member connections. Set this from the presence of accounts only.</comment>
<file context>
@@ -35,83 +18,35 @@ interface IntegrationListProps {
- detail: item.description,
+ description: item.description,
+ category: appCategory(item.providerId),
+ connected: item.accounts.length > 0 || item.availability === 'managed',
status: item.status,
- section: sectionFor(item),
</file context>
| connected: item.accounts.length > 0 || item.availability === 'managed', | |
| connected: item.accounts.length > 0, |
| const host = useWorkspaceHostContext() | ||
| const workspaceId = host.workspace.id | ||
| const destination = workspaceIntegrationsPath(workspaceId, host) | ||
| if (destination !== `/workspace/${workspaceId}/integrations`) redirect(destination) |
There was a problem hiding this comment.
P2: This redirect drops the workspace page’s category and search query state, so filtered or bookmarked integration URLs open the organization list unfiltered. Translate the supported state (at least search to the organization route’s q) before redirecting.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At apps/sim/app/workspace/[workspaceId]/integrations/integrations.tsx, line 167:
<comment>This redirect drops the workspace page’s `category` and `search` query state, so filtered or bookmarked integration URLs open the organization list unfiltered. Translate the supported state (at least `search` to the organization route’s `q`) before redirecting.</comment>
<file context>
@@ -159,6 +161,10 @@ function ConnectedItem({ href, blockType, name, description, icon: Icon }: Conne
+ const host = useWorkspaceHostContext()
+ const workspaceId = host.workspace.id
+ const destination = workspaceIntegrationsPath(workspaceId, host)
+ if (destination !== `/workspace/${workspaceId}/integrations`) redirect(destination)
return (
<PermissionAccessBoundary configKey='hideIntegrationsTab'>
</file context>
| label={`Search ${current}`} | ||
| placeholder={`Search ${current === 'mcps' ? 'MCPs' : current}…`} | ||
| value={view.q} | ||
| onValueChange={(q) => void setView({ q })} |
There was a problem hiding this comment.
P2: This writes the URL for every search keystroke, enqueueing a replace/navigation update while the user types. Use useDebouncedSearchSetter for q so the input remains responsive without issuing one URL update per key.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At apps/sim/platform/app/(shell)/integrations/_components/integration-center/integration-center.tsx, line 158:
<comment>This writes the URL for every search keystroke, enqueueing a replace/navigation update while the user types. Use `useDebouncedSearchSetter` for `q` so the input remains responsive without issuing one URL update per key.</comment>
<file context>
@@ -0,0 +1,306 @@
+ label={`Search ${current}`}
+ placeholder={`Search ${current === 'mcps' ? 'MCPs' : current}…`}
+ value={view.q}
+ onValueChange={(q) => void setView({ q })}
+ />
+ </div>
</file context>
| <h1 data-type='T076'>Integrations</h1> | ||
| <p data-type='T048'>Connect your accounts to the apps your organization makes available.</p> | ||
| <p data-type='T048'> | ||
| Connect your tools, share with your team, and bring everything into Sim. |
There was a problem hiding this comment.
P3: This shared heading promises team sharing on the live route, although live integrations render IntegrationList without the fixture-only sharing controls. Keep the live-safe copy here or make the heading mode-specific.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At apps/sim/platform/app/(shell)/integrations/_components/integrations-page/integrations-page.tsx, line 36:
<comment>This shared heading promises team sharing on the live route, although live integrations render `IntegrationList` without the fixture-only sharing controls. Keep the live-safe copy here or make the heading mode-specific.</comment>
<file context>
@@ -32,7 +32,9 @@ export function IntegrationsHeading({ handoff }: IntegrationsHeadingProps) {
<h1 data-type='T076'>Integrations</h1>
- <p data-type='T048'>Connect your accounts to the apps your organization makes available.</p>
+ <p data-type='T048'>
+ Connect your tools, share with your team, and bring everything into Sim.
+ </p>
{handoff}
</file context>
| Connect your tools, share with your team, and bring everything into Sim. | |
| Connect your accounts to the apps your organization makes available. |
[Medium impact] The changes affect the organization integration center and eligible workspace entry points. Preserve workspace credential browsing before merging; the personal-secrets return link also needs a small follow-up. FindingsSummaryConsolidates integrations into a shared directory:
Two gaps remain: workspace credential browsing is replaced by a different account inventory, and the live personal-secrets return link drops the new directory state.
|
| const host = useWorkspaceHostContext() | ||
| const workspaceId = host.workspace.id | ||
| const destination = workspaceIntegrationsPath(workspaceId, host) | ||
| if (destination !== `/workspace/${workspaceId}/integrations`) redirect(destination) |
There was a problem hiding this comment.
Workspace credentials disappear from browsing
This redirects eligible members away from the workspace credential directory to a different list of member accounts. For example, the workspace page lists GitLab personal tokens, but the organization center treats GitLab as managed and supplies no accounts. Members lose the normal way to find and manage those credentials, and opening the old directory URL redirects them again.
Keep a workspace-credential entry reachable until the center supports these records and actions.
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
| const memberSecretsHref = serialize( | ||
| integrationCenterHref(memberSecretsPath ?? defaults.memberSecrets, params), | ||
| { q: search } | ||
| ) |
There was a problem hiding this comment.
The live personal-secrets link now carries tab=credentials, filters, and sorting, but the editor's return link still reads and saves only q. Clicking Integrations after managing personal secrets therefore returns to Apps and loses the selected filters and sorting.
Update the editor's return-link builder to preserve the center state too.
Consolidates organization integrations into one Apps, Skills, MCPs and Credentials center. Eligible workspace entry points open the organization center while standalone and viewer-gated credential routes retain compatibility. The existing live Search inventory and authorized operations remain intact.
App details now support multiple credentials, provider-specific enrollment choices, credential descriptions and scoped access management. Connection fields are generated from existing provider definitions, including OAuth, personal tokens, service accounts, Google JSON and custom Slack bots. Canonical credential IDs stay consistent across compatible apps. New credential, sharing and resource interactions remain behind the server fixture flag; no provider authorization or secret mutation is ported.
The full workspace catalog supplies 275 app summaries with canonical icons, categories, suggested skills and templates. Only the selected app’s rich content reaches its detail. Shared Stack renders people and integration artwork with subtle hover movement and passive compact +N counts, including calendar attendees. App-level actions use the control bar; credential actions stay with their record. Home’s compact menus, centralized EMCN controls and tracked styling/ownership rules are reused throughout.
Depends on #9687 and targets its branch to keep the integration work separate from organization wiring.
Validation: 90 Platform browser checks, the real Electron renderer check, production build, root lint, 29 workspace type checks, 67 audits, the root test suite (20 workspaces; 39,617 app tests passed), and the block-registry audit passed. Browser coverage includes provider-specific forms, scope/role restrictions, final-admin protection, shared credential propagation, immediate browser Back, secret isolation, templates, responsive layouts and fixture-off output. Independent agents reviewed implementation and contracts; light/dark desktop, tablet and narrow layouts were inspected.
New flows are UI-only. Existing external OAuth mutations were preserved but not exercised against providers. Slack’s deployable manifest awaits an authorized service that supplies real credential IDs and callback URLs.