Skip to content

Commit f01572b

Browse files
committed
Put Connections and Capabilities beside Bots, with Connections as one list
Connections (/connections) is its own page and one searchable list: your apps, apps on the organisation's account, and your GitHub and Slack links, each opening its own page. Capabilities (/capabilities) holds Skills and Memory as tabs under its title. The sidebar is Bots, Capabilities, Connections. Old addresses under /settings and /skills redirect, carrying their query parameters. Every settings screen shares one 720px column. Explore agents on the home page shows white cards with the avatar on top.
1 parent e67a76d commit f01572b

90 files changed

Lines changed: 2641 additions & 1917 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.

‎.claude/skills/openbot-screen-layout/SKILL.md‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,9 @@ PageShell the page: header (title, description, action, backButton) then
5151
PageEmpty what a section says when it has nothing to list
5252
```
5353

54-
`PageShell` takes a `width`: `prose` (`max-w-2xl`) is the default because configuration is mostly
55-
reading, and a row of label-and-control has no business being wider than the sentence explaining it.
54+
`PageShell` takes a `width`: `prose` (`max-w-3xl`, 720px here) is the default, and every
55+
configuration screen uses it so no screen is wider than another. Sentences inside it are held to
56+
`max-w-prose`, so the column has room for a label beside its control without stretching the text.
5657
`wide` (`max-w-5xl`) exists for one screen — `admin/audit.tsx`, because an audit log is a table to be
5758
scanned and prose width would wrap every row. It is not a licence for anything else to be wide.
5859

‎CHANGELOG.md‎

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,31 @@ Newest first. `Unreleased` is what is on `main` and not yet tagged.
3535
request for an administrator to switch on a work app, grant it to a Bot, or connect a Shared app's
3636
team account is kept, with who answered it and when. It starts empty and nothing is backfilled.
3737

38+
### Every settings screen is the same, slightly wider column
39+
40+
Settings, Admin, Connections, Capabilities and each Bot's pages share one column width, now 720px instead of 630px, so
41+
no screen is wider than another and a row has room for its label beside its control. Sentences keep
42+
their comfortable reading width inside it. The audit log stays wider, because it is a table.
43+
44+
### Capabilities and Connections sit beside Bots in the sidebar
45+
46+
The sidebar's Skills entry becomes two, below Bots: **Capabilities** (`/capabilities`), with Skills
47+
(`/capabilities/skills`) and Memory (`/capabilities/memory`) as tabs under its title, Memory leaving
48+
Settings; and **Connections** (`/connections`), the apps your Bots use as you, which leaves Settings
49+
too. On the home page, Explore agents shows each Bot as a white card with its avatar on top, the same
50+
card as elsewhere. The old
51+
addresses — `/skills`, `/settings/connected-accounts` with any account beneath it, and
52+
`/settings/memory` — redirect to the new ones, keeping `?connected=`, `?linked=` and a skill being
53+
written or edited, so a consent begun before upgrading still lands on its page. OAuth, GitHub linking
54+
and the Slack link confirmation now send people to `/connections`.
55+
56+
Connections is one searchable list. Apps you connect, apps on your organisation's account (marked
57+
"Team account") and your GitHub and Slack links are rows of the same kind, with those already in use
58+
first, and each opens its own page. GitHub's page holds your link, the repositories whose events can
59+
start a responsibility (formerly "GitHub events"), and a way to a Composio GitHub app where one is
60+
enabled, so GitHub is listed once; linking GitHub now comes back to that page. The reset for what
61+
you let shared Bots use moves to Settings → Approvals, as **Shared Bots**.
62+
3863
### Each app on a Bot says whether you can use it, and you can ask an administrator for the rest
3964

4065
A Bot's page now says, for every work app the Bot holds, what that app is for the person looking:

‎README.md‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -193,14 +193,14 @@ Leave `EMBEDDED_POSTGRES` off and set `DATABASE_URL` to point at a database you
193193
| `/bots/:id/reach` | Continue a conversation with the Bot in Slack, Microsoft Teams, or by text message. |
194194
| `/bots/:id/memory` | Which connected apps feed the Bot facts, and its background research. |
195195
| `/bots/:id/approvals` | What the Bot may do without asking you first. |
196-
| `/skills` | Create and enable personal skills. |
196+
| `/connections` | Connect your apps so your Bots can work with them. |
197+
| `/capabilities/skills` | Create and enable personal skills. |
198+
| `/capabilities/memory` | What your Bots remember about you, and facts you tell them. |
197199
| `/routines` | See the routines that are standing, and stop one. |
198200
| `/settings` | User preferences. |
199-
| `/settings/connected-accounts` | Connect your apps so your Bots can work with them. |
200201
| `/settings/passwords` | Logins you saved when signing a Bot in to a website. |
201202
| `/settings/notifications` | Where updates go, your devices, and recent deliveries. |
202203
| `/settings/approvals` | Whether your Bots ask before acting, and rules for every Bot. |
203-
| `/settings/memory` | What your Bots remember about you, and facts you tell them. |
204204
| `/admin/credentials` | Store write-only encrypted credentials. |
205205
| `/admin/approvals` | Team approval settings, team rules, and shared account requests. |
206206
| `/admin/computers` | View, stop, and reset Bot computers. |

‎app/src/components/agents/agent-card.tsx‎

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import { IconArrowRight } from "@tabler/icons-react";
22
import { Link } from "@tanstack/react-router";
3-
import Avatar from "boring-avatars";
43
import { AbstractAvatar } from "@/components/agents/abstract-avatar";
54
import { Button } from "@/components/ui/button";
65
import type { AgentProfile } from "@/lib/agents/queries";
@@ -10,21 +9,23 @@ export function AgentCard({
109
appearance = "compact",
1110
}: {
1211
agent: AgentProfile;
13-
appearance?: "compact" | "artwork";
12+
appearance?: "compact" | "tile";
1413
}) {
15-
if (appearance === "artwork") {
14+
if (appearance === "tile") {
1615
return (
17-
<div className="relative h-[180px] w-[144px] overflow-hidden rounded-2xl bg-foreground/10">
18-
<div
19-
aria-hidden="true"
20-
className="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2"
21-
>
22-
<Avatar name={agent.avatarSeed} size={250} />
16+
<div className="flex h-[180px] w-[144px] flex-col overflow-hidden rounded-2xl border border-border bg-card p-3 transition-colors hover:border-ring dark:border-transparent">
17+
<div aria-hidden="true" className="flex shrink-0">
18+
<AbstractAvatar name={agent.name} seed={agent.avatarSeed} size={36} />
2319
</div>
24-
<div className="absolute top-0 left-0 h-full w-full bg-background/40 dark:bg-background/50" />
25-
<div className="absolute top-0 left-0 flex h-full w-full flex-col justify-end gap-2 p-3">
26-
<span className="line-clamp-1 text-sm font-medium">{agent.name}</span>
27-
<span className="line-clamp-3 text-xs text-foreground dark:text-foreground/80">
20+
{/* Bottom-aligned, where the text sat on the earlier artwork card; the avatar holds the top. */}
21+
<div className="mt-auto flex min-w-0 flex-col gap-2">
22+
<span
23+
className="line-clamp-1 break-words text-sm font-medium leading-5"
24+
title={agent.name}
25+
>
26+
{agent.name}
27+
</span>
28+
<span className="line-clamp-3 break-words text-xs leading-4 text-muted-foreground">
2829
{agent.roleDescription}
2930
</span>
3031
</div>

‎app/src/components/app-sidebar/app-sidebar.tsx‎

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import {
22
IconBolt,
33
IconBox,
44
IconLogout,
5+
IconPlug,
56
IconPlus,
67
IconSearch,
78
IconSettings,
@@ -366,14 +367,18 @@ export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
366367
<BotsNavBadge />
367368
</SidebarMenuButton>
368369
</SidebarMenuItem>
370+
{/*
371+
* Beside Bots rather than inside Admin: each is somebody's own. Capabilities are your skills
372+
* and what your Bots remember about you; Connections are the apps a Bot reaches as you.
373+
* Capabilities links to `/capabilities` rather than a tab, so it stays lit on both tabs.
374+
*/}
369375
<SidebarMenuItem>
370-
{/* Beside Bots rather than inside Admin: writing a skill is something anybody does. */}
371376
<SidebarMenuButton
372377
className="hover:bg-foreground/5 h-10"
373378
render={(props) => (
374379
<Link
375380
{...props}
376-
to="/skills"
381+
to="/capabilities"
377382
activeProps={{
378383
className: "bg-foreground/5",
379384
}}
@@ -383,7 +388,26 @@ export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
383388
<div className="size-[28px] flex items-center justify-center">
384389
<IconBox />
385390
</div>
386-
<span className="text-sm trackint-tight">Skills</span>
391+
<span className="text-sm trackint-tight">Capabilities</span>
392+
</SidebarMenuButton>
393+
</SidebarMenuItem>
394+
<SidebarMenuItem>
395+
<SidebarMenuButton
396+
className="hover:bg-foreground/5 h-10"
397+
render={(props) => (
398+
<Link
399+
{...props}
400+
to="/connections"
401+
activeProps={{
402+
className: "bg-foreground/5",
403+
}}
404+
/>
405+
)}
406+
>
407+
<div className="size-[28px] flex items-center justify-center">
408+
<IconPlug />
409+
</div>
410+
<span className="text-sm trackint-tight">Connections</span>
387411
</SidebarMenuButton>
388412
</SidebarMenuItem>
389413
<SidebarMenuItem>

‎app/src/components/bot-profile/access.tsx‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,10 @@ function YourSkills({ agentId }: { agentId: string }) {
116116
) : skills.length === 0 ? (
117117
<PageEmpty>
118118
You have not written a skill yet.{" "}
119-
<Link className="underline underline-offset-4" to="/skills">
119+
<Link
120+
className="underline underline-offset-4"
121+
to="/capabilities/skills"
122+
>
120123
Write one
121124
</Link>
122125
.
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import { Link, type LinkOptions, useMatchRoute } from "@tanstack/react-router";
2+
import type * as React from "react";
3+
import { PageShell } from "@/components/layout/page-shell";
4+
import { Button } from "@/components/ui/button";
5+
6+
/**
7+
* What a person gives their own Bots beyond the apps they connect: the skills they write and what
8+
* their Bots remember about them. Connections, the apps a Bot reaches as them, is its own page.
9+
*
10+
* Skills used to sit beside Bots and Memory in Settings, though both answer the same question: what
11+
* do my Bots know how to do, and what do they know about me?
12+
*/
13+
const TABS = [
14+
{ label: "Skills", linkOptions: { to: "/capabilities/skills" } },
15+
{ label: "Memory", linkOptions: { to: "/capabilities/memory" } },
16+
] as const satisfies readonly { label: string; linkOptions: LinkOptions }[];
17+
18+
/**
19+
* The tabs are links drawn as buttons. Each page keeps its own address, so it can be bookmarked,
20+
* opened in a new tab and reached by Back. The match is fuzzy so a tab stays lit on a page beneath
21+
* it.
22+
*/
23+
export function CapabilitiesTabs() {
24+
const matchRoute = useMatchRoute();
25+
return (
26+
<nav aria-label="Capabilities" className="mt-6 flex items-center gap-1">
27+
{TABS.map((tab) => (
28+
<Button
29+
key={tab.label}
30+
render={(props) => <Link {...tab.linkOptions} {...props} />}
31+
variant={
32+
matchRoute({ ...tab.linkOptions, fuzzy: true })
33+
? "secondary"
34+
: "ghost"
35+
}
36+
>
37+
{tab.label}
38+
</Button>
39+
))}
40+
</nav>
41+
);
42+
}
43+
44+
/**
45+
* The frame each tab draws itself in: the Capabilities title with the tabs beneath it, and no
46+
* description, since every section beneath them has its own title and says what it is for.
47+
*
48+
* A component each page renders rather than the `/capabilities` layout route drawing it once,
49+
* because Skills wraps its whole page — heading included — in the side panel that writes and edits a
50+
* skill. A heading drawn by the layout would sit outside that panel and stay full width while it is
51+
* open.
52+
*/
53+
export function CapabilitiesShell({ children }: { children: React.ReactNode }) {
54+
return (
55+
<PageShell className="@container" title="Capabilities">
56+
<CapabilitiesTabs />
57+
{children}
58+
</PageShell>
59+
);
60+
}

‎app/src/components/channels/connect-app-card.tsx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ export function ConnectAppCard({
9595
<Link
9696
className="underline underline-offset-2"
9797
params={{ key: serverId }}
98-
to="/settings/connected-accounts/$key"
98+
to="/connections/$key"
9999
>
100100
Enter it on {title}'s page
101101
</Link>

‎app/src/components/computer/demonstration-recorder.tsx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -353,7 +353,7 @@ export function DemonstrationRecorder({
353353
{recording.status === "published" && recording.skillSlug ? (
354354
<>
355355
<Link
356-
to="/skills"
356+
to="/capabilities/skills"
357357
search={{ edit: recording.skillSlug }}
358358
className="text-xs underline"
359359
>
Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
import { IconBrandGithub, IconChevronRight } from "@tabler/icons-react";
2+
import { useQuery } from "@tanstack/react-query";
3+
import { Link, useNavigate } from "@tanstack/react-router";
4+
import { useEffect, useState } from "react";
5+
import {
6+
PageRows,
7+
PageSection,
8+
PageShell,
9+
} from "@/components/layout/page-shell";
10+
import { GithubSources } from "@/components/settings/github-sources";
11+
import {
12+
type LinkableProvider,
13+
LinkedAccountSection,
14+
linkableProviderTitle,
15+
} from "@/components/settings/linked-accounts";
16+
import {
17+
linkedNotice,
18+
visibleLinkedNotice,
19+
} from "@/components/settings/linked-notice";
20+
import {
21+
Item,
22+
ItemActions,
23+
ItemContent,
24+
ItemDescription,
25+
ItemMedia,
26+
ItemTitle,
27+
} from "@/components/ui/item";
28+
import { IDENTITY_SUMMARIES, isBrokeredGithub } from "@/lib/connections/rows";
29+
import { linkedAccountsQueryOptions } from "@/lib/identity/queries";
30+
import {
31+
connectionsQueryOptions,
32+
personalConnections,
33+
pluginsPageQueryOptions,
34+
} from "@/lib/plugins/queries";
35+
36+
/**
37+
* GitHub's or Slack's page under Connections: the person's own account there, and for GitHub what
38+
* hangs off it.
39+
*
40+
* These used to be a "Linked accounts" section and a "GitHub events" section on the Connections
41+
* page itself, beside the apps, so a person had to know a link was a different kind of thing from an
42+
* app before they could find it. Now each is a row like any app, and this is the row's page.
43+
*
44+
* `linked` is what the GitHub callback sends back, `?linked=github` and its failures. It is said
45+
* once and then dropped from the address, so a reload does not repeat the news.
46+
*/
47+
export function IdentityConnectionPage({
48+
linked,
49+
provider,
50+
}: {
51+
linked: string | undefined;
52+
provider: LinkableProvider;
53+
}) {
54+
const title = linkableProviderTitle(provider);
55+
const [arrival] = useState(() => linkedNotice(linked));
56+
const links = useQuery(linkedAccountsQueryOptions());
57+
const notice = visibleLinkedNotice(arrival, links.data);
58+
const navigate = useNavigate();
59+
useEffect(() => {
60+
if (linked !== undefined) {
61+
navigate({
62+
to: ".",
63+
search: (previous: Record<string, unknown>) => {
64+
const { linked: _linked, ...rest } = previous;
65+
return rest;
66+
},
67+
replace: true,
68+
});
69+
}
70+
}, [linked, navigate]);
71+
72+
return (
73+
<PageShell
74+
backButton={{ label: "Connections", linkProps: { to: "/connections" } }}
75+
description={IDENTITY_SUMMARIES[provider]}
76+
title={title}
77+
>
78+
{notice ? (
79+
<p
80+
className={
81+
notice.tone === "error"
82+
? "mt-4 text-destructive text-sm"
83+
: "mt-4 text-muted-foreground text-sm"
84+
}
85+
role={notice.tone === "error" ? "alert" : "status"}
86+
>
87+
{notice.text}
88+
</p>
89+
) : null}
90+
<LinkedAccountSection provider={provider} />
91+
{provider === "github" ? (
92+
<>
93+
<GithubSources />
94+
<GithubTools />
95+
</>
96+
) : null}
97+
</PageShell>
98+
);
99+
}
100+
101+
/**
102+
* A brokered GitHub app, where one is enabled. It is a separate connection — tools that read and
103+
* change repositories as you — and the Connections list leaves its row out so GitHub is listed once,
104+
* which makes this the way to it.
105+
*/
106+
function GithubTools() {
107+
const plugins = useQuery(pluginsPageQueryOptions());
108+
const connections = useQuery(connectionsQueryOptions());
109+
const app = plugins.data?.composioConfigured
110+
? plugins.data.servers.find(
111+
(server) => isBrokeredGithub(server) && server.accountMode !== "shared",
112+
)
113+
: undefined;
114+
if (!app) return null;
115+
const connected = personalConnections(
116+
connections.data?.connections ?? [],
117+
).some((row) => row.serverId === app.id);
118+
return (
119+
<PageSection
120+
description="Lets your Bots read and change your repositories as you. A connection of its own, separate from your account above."
121+
title="Tools"
122+
>
123+
<PageRows>
124+
<Item
125+
render={<Link params={{ key: app.id }} to="/connections/$key" />}
126+
size="sm"
127+
>
128+
<ItemMedia variant="icon">
129+
<IconBrandGithub />
130+
</ItemMedia>
131+
<ItemContent>
132+
<ItemTitle>GitHub tools</ItemTitle>
133+
<ItemDescription>
134+
{connected ? "Connected" : "Not connected"}
135+
</ItemDescription>
136+
</ItemContent>
137+
<ItemActions>
138+
<IconChevronRight className="size-4 text-muted-foreground" />
139+
</ItemActions>
140+
</Item>
141+
</PageRows>
142+
</PageSection>
143+
);
144+
}

0 commit comments

Comments
 (0)