Skip to content

Commit a824d10

Browse files
committed
improvement(platform): expand integration catalog and credential management
1 parent 5cd02a6 commit a824d10

82 files changed

Lines changed: 4617 additions & 740 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎apps/sim/platform/AGENTS.md‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,11 @@ Home and Chat share a centered, border-box reading column (maximum width and inl
2727

2828
Use `ControlRow` for the sidebar, identity controls, panel tabs, recent links, wrapping workspace choices and horizontal destination links. The row owns its 30px height, 8px inset and the placement and hover of a 24px action, including a leading remove button that replaces an existing glyph. Workspace names, the Workspaces launcher and its tab title are text-only. A recent without a glyph keeps its remove action at the end. Keep the link, tab or button semantics on the rendered element, with sibling actions outside it. Whole section headers reveal their chevron on hover; identity controls need none. Ordinary buttons, icon triggers and rows share the sidebar Home radius (`--ui-control-radius`). Use `Button` / `IconButton` for actions and Base UI triggers, passing icons and labels through their slots. The composer toolbar owns its round actions. Switches and segmented controls keep their pill shape. Clicking the selected half of a two-choice segment switches to the other half. Native keyboard choice navigation still selects its destination.
2929

30+
Use `Stack` for compact people or integration artwork: show two items, a passive `+N` overflow count, and each item’s name on hover. Counts remain intrinsic-width and use compact notation from one thousand; never replace them with an ellipsis action. Each hovered item shifts left by the shared inset without moving its siblings. Use the shared motion duration and respect reduced motion. `AvatarGroup` supplies people to the same stack for integration usage and calendar attendees.
31+
3032
Use `ActivityRow` for a first-line glyph or avatar, title/detail and centered trailing metadata. Use the chart, calendar, composer or attachment owner when those parts have a different interaction. See the [component ownership map](docs/component-ownership.md) before adding another family.
3133

32-
Toolbar choices use `ChoiceMenu`, the same compact quiet button and menu as Home's activity period. A toolbar action uses the existing quiet compact Button or IconButton. `Select` and `SelectField` provide field chrome for forms and settings; do not place a full-width field picker in a control bar or invent a toolbar variant to disguise it. Reuse the existing owner for the interaction and compare its actual trigger with Home before accepting it.
34+
Toolbar choices use `ChoiceMenu`, the same compact quiet button and menu as Home's activity period. A toolbar action uses the existing quiet compact Button or IconButton. Page-level actions live in the control bar beneath the title and description; an action for a particular record belongs with that record. `Select` and `SelectField` provide field chrome for forms and settings; do not place a full-width field picker in a control bar or invent a toolbar variant to disguise it. Reuse the existing owner for the interaction and compare its actual trigger with Home before accepting it.
3335

3436
Measure the actual scroll viewport with `ScrollArea`, or attach `useScrollEdges` directly to a native or Base UI scroll owner. Use the shared 16px mask only on edges with more content, updating after scroll, resize, input, reset or filtering. Platform and Settings navigation hide the native scrollbar and keep their edge fades, keyboard, wheel and touch scrolling. Other scroll areas keep their existing scrollbar behavior. Insets establish resting layout; they cannot substitute for an edge fade. Home widgets and notification panels use `--ui-widget-max-size` as a ceiling, not a forced height. Keep the dashboard free to scroll and let each overflowing list or schedule scroll independently. Plain tooltips preview at most three lines; retain the complete accessible text. A structured people popover owns its content and uses the dropdown’s popup padding and row insets.
3537

‎apps/sim/platform/README.md‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ Open [Home](http://localhost:3100/home), [Chat](http://localhost:3100/chat/weekl
1515

1616
Settings uses the same organization catalog and section components in fixture and live routes. [General](http://localhost:3100/settings/general?state=full) includes Privacy and Account details; [Sources](http://localhost:3100/settings/integrations?state=full) is the administrator's Search source inventory. The [integration center](docs/integration-center.md) is the member-facing management surface for Apps, Skills, MCPs and Credentials. Settings examples are loaded only by the server's fixture flag. Their edits belong to the browser session; no account, credential, organization policy or subscription is changed by preview interactions.
1717

18+
Integration fixtures cover the full workspace app catalog, multiple credentials, OAuth and provider-specific service-account forms, access management, suggested skills and templates. App-level actions use the control bar; credential rows open their own details. Family credentials share a canonical ID across apps. New enrollment and sharing remain UI-only, with secrets confined to the open form. The existing live Search adapter keeps its own authorized inventory and actions until the broader credential seam is connected.
19+
1820
Settings fixtures default to Enterprise at every density. The [Team subscription](http://localhost:3100/settings/billing?state=stress&plan=team) and [Free subscription](http://localhost:3100/settings/billing?state=empty&plan=free) previews apply the selected plan to Billing, Members and the available Settings sections. The [Knowledge graphs preview](http://localhost:3100/settings/knowledge-graphs?state=stress) uses the stress viewer’s platform-admin role, benchmark access and enabled superuser mode.
1921

2022
The product routes live under `app/(shell)/`. Their shared App Router layout owns the shell, sidebar scroll viewport, interface preferences and chat edits. Home, Chat, Settings and Integrations pages render their own bodies inside that layout. Navigating between them keeps the sidebar reading position, including when returning from Settings. The shell selects its gated fixture and appearance from the current URL. Each route owns its components and CSS Modules in a private `_components/<component>/` folder: the [Home chart](app/%28shell%29/home/_components/activity-chart/activity-chart.tsx) sits beside its styles; the [dialog](design-system/components/modal/modal.tsx) belongs to EMCN. Shared shell UI lives in `app/(shell)/_components/`. [Rhythm tokens](design-system/styles/rhythm.css) and the [ownership and interaction contract](AGENTS.md) apply throughout the product. The [Paper state inventory](docs/core-chat-states.md) enumerates the source rows in scope. The [organization integrations guide](docs/organization-integrations.md) maps the member inventory, credential actions and tool permissions to their data operations. The integration center lives at the organization root; Home and eligible workspace links open that same destination. Standalone workspaces and viewers without organization Search access retain their existing credential route. Skills, MCPs and new sharing/editor interactions currently use only server-gated fixtures. Paper workspace tabs and the control bar inform the center's anatomy. Wiki is deferred.
@@ -65,7 +67,7 @@ Shared and Platform still own different recipes; `catalog/evidence.json` preserv
6567

6668
Workspace lists, sidebar items, recents and attachment batches accept typed arrays with stable item IDs. Use the named components for these collection masters: a Paper specimen's item count is sample data. An empty array renders zero items, and adding items preserves their registered row/card appearance. Each scroll viewport determines fades and controls from its actual contents. Chart labels, marks, inspected values and legends come from the same series. Supply raw numeric values and their units to data tables. The column schema determines alignment and which columns remain visible as the table narrows.
6769

68-
The full fixture includes twenty sidebar workspaces, sixteen conversations, a twelve-message inbox and a calendar that scrolls from midnight to midnight even on empty days. Home and Chat share a reading width. Home and Integrations share the same heading inset; the shell reserves sidebar opener and native titlebar clearance separately. The shell uses one 12px inset above the organization and on both sides of the profile row. The Home content scrolls through its viewport edge fade. The shell keeps the collapsed sidebar button aligned with the expanded toolbar even as the page scrolls. Platform and Settings navigation scroll independently, with edge fades instead of a visible scrollbar. Calendar events use the muted surface. An event with a provider URL opens in a new tab. The attendee control opens its own people popover. Now appears when the calendar scrolls away from its resting position or another day is selected. It restores today’s schedule and centers the current time. Scrolling back to that position also hides Now.
70+
The full fixture includes twenty sidebar workspaces, sixteen conversations, a twelve-message inbox and a calendar that scrolls from midnight to midnight even on empty days. Home and Chat share a reading width. Home and Integrations share the same heading inset; the shell reserves sidebar opener and native titlebar clearance separately. The shell uses one 12px inset above the organization and on both sides of the profile row. The Home content scrolls through its viewport edge fade. The shell keeps the collapsed sidebar button aligned with the expanded toolbar even as the page scrolls. Platform and Settings navigation scroll independently, with edge fades instead of a visible scrollbar. Calendar events use the muted surface. An event with a provider URL opens in a new tab. Attendees and integration usage share Stack: two photos with names on hover and a passive +N count, compacted for large totals. Now appears when the calendar scrolls away from its resting position or another day is selected. It restores today’s schedule and centers the current time. Scrolling back to that position also hides Now.
6971

7072
Start with the [component ownership map](docs/component-ownership.md) when choosing a primitive. Sidebar, recents and panel tabs use one row; form and command dialogs use one borderless surface. Copy confirmations and transient notifications last four seconds. The notification count uses the compact Invite button. Expanded notices share one subtle shadow around the list. A new notice pauses its expiry on hover or keyboard focus. Icon color transitions take 120ms and respect reduced motion. The [design contract](AGENTS.md) defines these rules.
7173

@@ -88,6 +90,7 @@ The [Platform design contract](AGENTS.md) is authoritative. Use the repository's
8890
- **Styles:** obtain Shared Light `get_tokens` (the token array) and `get_computed_styles` for the type masters (the node-to-style map); confirm theme file hashes match before assuming identical vocabulary. Run `bun scripts/sync-platform-foundations.ts <tokens.json> <registry.json> <styles.json>`, or append `--check` to compare without writing. This preserves aliases and converts scalable Paper px to rem at the 16px reference scale. Roles and material composites live in `design-system/styles`. The Tailwind entry scans only component owners; product pages do not receive a styling API.
8991
- **Structure:** capture current Shared/Platform master JSX locally with node IDs for the compiler's anatomy check in `<exports>/<key>.jsx` and reconcile the registry and recipe contracts. Run `bun scripts/sync-platform-recipes.ts <paper-references-directory> <exports-directory>` from the repository root. Review changed keys, token aliases, CSS and nested slot geometry in the rendered workbench. Do not edit generated selectors to fix a product use.
9092
- **Assets:** retain canonical glyph geometry and brand paints. Media belongs in named slots; do not fetch private Paper artwork at runtime. Fixture content is removable from `fixtures/`. Remove its loader, preview calls and synthetic data together when real resource reads arrive.
93+
- **Integration catalog:** run `bun scripts/sync-platform-integration-metadata.ts` after changing public provider metadata, connection definitions, skills, templates or registry icons. The generated server-only catalog supplies selected detail content; the directory receives compact summaries. `bun run check:platform-integration-metadata` detects drift. Runtime product code never imports the executable block/tool registry.
9194

9295
## Verification
9396

Lines changed: 80 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,14 @@
11
'use client'
22

33
import { createContext, type ReactNode, useContext, useState } from 'react'
4+
import type { IntegrationCredential } from '@/platform/lib/integrations/credential-model'
45
import type {
56
IntegrationsData,
67
OrganizationIntegration,
78
} from '@/platform/lib/integrations/fixture-model'
89

9-
type AccountEdits = Pick<
10-
OrganizationIntegration,
11-
'account' | 'connectableMethod' | 'connectionCandidate'
12-
>
13-
1410
function useEdits() {
15-
return useState<Record<string, AccountEdits>>({})
11+
return useState<Record<string, IntegrationCredential | null>>({})
1612
}
1713

1814
const Changes = createContext<ReturnType<typeof useEdits> | null>(null)
@@ -26,25 +22,89 @@ export function IntegrationChanges({ children }: IntegrationChangesProps) {
2622
return <Changes.Provider value={edits}>{children}</Changes.Provider>
2723
}
2824

25+
function metadata(credential: IntegrationCredential): IntegrationCredential {
26+
const {
27+
id,
28+
appId,
29+
providerId,
30+
type,
31+
displayName,
32+
description,
33+
accountLabel,
34+
instanceUrl,
35+
role,
36+
status,
37+
canReconnect,
38+
scope,
39+
members,
40+
permissions,
41+
} = credential
42+
return {
43+
id,
44+
appId,
45+
providerId,
46+
type,
47+
displayName,
48+
description,
49+
accountLabel,
50+
instanceUrl,
51+
role,
52+
status,
53+
canReconnect,
54+
scope,
55+
permissions,
56+
members: members.map(
57+
({ id, userId, name, email, src, role, roleSource, explicitAdmin, status }) => ({
58+
id,
59+
userId,
60+
name,
61+
email,
62+
src,
63+
role,
64+
roleSource,
65+
explicitAdmin,
66+
status,
67+
})
68+
),
69+
}
70+
}
71+
72+
/** A family credential is edited by its canonical ID across every app that displays it. */
2973
export function useOrganizationIntegrations(data: IntegrationsData | null) {
3074
const edits = useContext(Changes)
3175
if (!edits) throw new Error('Integration controls must be inside the product layout')
32-
const [accounts, setAccounts] = edits
76+
const [credentials, setCredentials] = edits
77+
const roster = new Map<string, IntegrationCredential | null>(
78+
data?.integrations.flatMap((item) =>
79+
item.credentials.map((credential) => [credential.id, credential] as const)
80+
)
81+
)
82+
for (const [id, credential] of Object.entries(credentials)) roster.set(id, credential)
83+
const visible = [...roster.values()].filter((credential): credential is IntegrationCredential =>
84+
Boolean(credential)
85+
)
86+
const integrations = (data?.integrations ?? []).map((item) => ({
87+
...item,
88+
credentials: visible.filter(
89+
(credential) =>
90+
credential.appId === item.providerId ||
91+
item.credentialProviders.includes(credential.providerId)
92+
),
93+
}))
3394
return {
34-
integrations: (data?.integrations ?? []).map((item) => ({
35-
...item,
36-
...accounts[item.providerId],
37-
})),
95+
integrations,
3896
save(item: OrganizationIntegration) {
39-
if (!data?.integrations.some(({ providerId }) => providerId === item.providerId)) return
40-
setAccounts((current) => ({
41-
...current,
42-
[item.providerId]: {
43-
account: item.account,
44-
connectableMethod: item.connectableMethod,
45-
connectionCandidate: item.connectionCandidate,
46-
},
47-
}))
97+
const previous = integrations.find(({ providerId }) => providerId === item.providerId)
98+
if (!data || !previous) return
99+
setCredentials((current) => {
100+
const next = { ...current }
101+
const retained = new Set(item.credentials.map(({ id }) => id))
102+
for (const credential of previous.credentials) {
103+
if (!retained.has(credential.id)) next[credential.id] = null
104+
}
105+
for (const credential of item.credentials) next[credential.id] = metadata(credential)
106+
return next
107+
})
48108
},
49109
}
50110
}

‎apps/sim/platform/app/(shell)/integrations/_components/connected-credential/connected-credential.tsx‎

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import { useState } from 'react'
44
import Link from 'next/link'
55
import styles from '@/platform/app/(shell)/integrations/_components/connected-credential/connected-credential.module.css'
6-
import { ActionMenu, Button, Modal } from '@/platform/design-system/components'
6+
import { ActionMenu, Modal } from '@/platform/design-system/components'
77
import type { IntegrationAccount } from '@/platform/lib/data/integrations-types'
88

99
interface CredentialRow extends IntegrationAccount {
@@ -16,8 +16,6 @@ interface ConnectedCredentialProps {
1616
integrationName: string
1717
accounts: readonly CredentialRow[]
1818
pending: boolean
19-
/** Connects a further account into a free slot, when the organization offers one. */
20-
addAccount?: { disabled: boolean; onSelect: () => void }
2119
onReconnect: (accountId: string) => void
2220
onDisconnect: (accountId: string) => Promise<unknown>
2321
}
@@ -26,7 +24,6 @@ export function ConnectedCredential({
2624
integrationName,
2725
accounts,
2826
pending,
29-
addAccount,
3027
onReconnect,
3128
onDisconnect,
3229
}: ConnectedCredentialProps) {
@@ -75,13 +72,6 @@ export function ConnectedCredential({
7572
/>
7673
</div>
7774
))}
78-
{addAccount && (
79-
<div>
80-
<Button disabled={pending || addAccount.disabled} onClick={addAccount.onSelect}>
81-
Add account
82-
</Button>
83-
</div>
84-
)}
8575
{confirming && (
8676
<Modal
8777
open

‎apps/sim/platform/app/(shell)/integrations/_components/connection-access/connection-access.tsx‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,10 @@ function AccessGroup({ title, items }: AccessGroupProps) {
3030
}
3131

3232
interface ConnectionAccessProps {
33-
integration: OrganizationIntegration
33+
integration: Pick<
34+
OrganizationIntegration,
35+
'searchReads' | 'searchResource' | 'requestedScopes' | 'accessDescription'
36+
> & { availability: string }
3437
}
3538

3639
export function ConnectionAccess({ integration }: ConnectionAccessProps) {
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
.section {
2+
display: grid;
3+
min-inline-size: 0;
4+
gap: var(--ui-section-gap);
5+
}
6+
.heading,
7+
.actions {
8+
display: flex;
9+
align-items: center;
10+
gap: var(--ui-action-gap);
11+
}
12+
.heading {
13+
justify-content: space-between;
14+
}

0 commit comments

Comments
 (0)