Skip to content

Commit 8550993

Browse files
committed
feat(web): overhaul shared-skills admin table and sharing UX
Replace the workspace shared-skills card list with a searchable, filterable table (Synced/Manual filter, Added by, Auto toggle), backed by source and creator fields on the management item. Sharing a synced skill now goes through a confirmation dialog warning about owner/member repo visibility, shown only when permission syncing is on. Add cross-navigation between the account and admin skill views, and drop the redundant Make personal menu item in favor of the Shared toggle.
1 parent 8fdfc92 commit 8550993

12 files changed

Lines changed: 749 additions & 218 deletions

File tree

packages/web/src/app/(app)/settings/skills/page.tsx

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,19 +5,29 @@ import { ServiceErrorException } from "@/lib/serviceError";
55
import { isServiceError } from "@/lib/utils";
66
import { authenticatedPage } from "@/middleware/authenticatedPage";
77
import { OrgRole } from "@sourcebot/db";
8+
import { env } from "@sourcebot/shared";
89
import { SkillsEntitlementMessage } from "./skillsEntitlementMessage";
910

10-
export default authenticatedPage(async ({ user, role }) => {
11+
interface PageProps extends Record<string, unknown> {
12+
// `skill` deep-links a specific skill as the initially-selected one (used by
13+
// the workspace admin table's skill-name links).
14+
searchParams: Promise<{ skill?: string }>;
15+
}
16+
17+
export default authenticatedPage<PageProps>(async ({ user, role }, { searchParams }) => {
1118
// Skills are part of Ask Sourcebot. Gate the EE skill UI behind the `ask`
1219
// entitlement so it never renders on a non-entitled deployment; show the FSL
1320
// upsell panel instead.
1421
if (!(await hasEntitlement('ask'))) {
1522
return <SkillsEntitlementMessage />;
1623
}
1724

18-
const [personalSkills, sharedSkills] = await Promise.all([
19-
listPersonalAgentSkills(),
20-
listSharedAgentSkillCatalog(),
25+
const [{ skill: initialSelectedId }, [personalSkills, sharedSkills]] = await Promise.all([
26+
searchParams,
27+
Promise.all([
28+
listPersonalAgentSkills(),
29+
listSharedAgentSkillCatalog(),
30+
]),
2131
]);
2232
if (isServiceError(personalSkills)) {
2333
throw new ServiceErrorException(personalSkills);
@@ -32,6 +42,8 @@ export default authenticatedPage(async ({ user, role }) => {
3242
initialSharedSkills={sharedSkills}
3343
currentUserEmail={user.email ?? ""}
3444
isOwner={role === OrgRole.OWNER}
45+
initialSelectedId={initialSelectedId}
46+
permissionSyncEnabled={env.PERMISSION_SYNC_ENABLED === 'true'}
3547
/>
3648
);
3749
});

packages/web/src/app/(app)/settings/workspaceAskAgent/page.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ interface PageProps extends Record<string, unknown> {
1313
status?: string;
1414
server?: string;
1515
message?: string;
16+
// Pre-fills the shared-skills search box (deep link from the account page).
17+
skillSearch?: string;
1618
}>;
1719
}
1820

@@ -34,7 +36,7 @@ export default authenticatedPage<PageProps>(async ({ org, prisma }, { searchPara
3436
}
3537
}
3638

37-
const { status, server, message } = await searchParams;
39+
const { status, server, message, skillSearch } = await searchParams;
3840
const orgSkills = hasAskEntitlement ? await listSharedAgentSkillManagement() : [];
3941
if (isServiceError(orgSkills)) {
4042
throw new ServiceErrorException(orgSkills);
@@ -47,6 +49,7 @@ export default authenticatedPage<PageProps>(async ({ org, prisma }, { searchPara
4749
callbackMessage={message}
4850
oauthRedirectUrl={getMcpOAuthCallbackUrl()}
4951
initialOrgSkills={orgSkills}
52+
initialSkillSearch={skillSearch}
5053
/>
5154
);
5255
}, { minRole: OrgRole.OWNER, redirectTo: '/settings' });

packages/web/src/app/(app)/settings/workspaceAskAgent/workspaceAskAgentPage.test.tsx

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { act, cleanup, fireEvent, render, screen, waitFor, within } from '@testi
22
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
33
import { afterEach, describe, expect, test, vi } from 'vitest';
44
import { TooltipProvider } from '@/components/ui/tooltip';
5+
import type { ReactNode } from 'react';
56
import type { SharedAgentSkillManagementItem } from '@/ee/features/chat/skills/types';
67

78
vi.mock('next/navigation', () => ({
@@ -10,6 +11,15 @@ vi.mock('next/navigation', () => ({
1011
}),
1112
}));
1213

14+
// The shared-skills manager links skill names via next/link; render a plain anchor.
15+
vi.mock('next/link', async () => {
16+
const { createElement } = await import('react');
17+
return {
18+
default: ({ href, children }: { href: string; children: ReactNode }) =>
19+
createElement('a', { href }, children),
20+
};
21+
});
22+
1323
vi.mock('@/app/api/(client)/client', () => ({
1424
getMcpConfiguration: vi.fn(),
1525
getMcpServersWithStatus: vi.fn(),
@@ -81,6 +91,8 @@ describe('WorkspaceAskAgentPage', () => {
8191
description: 'Review risky changes.',
8292
enabled: true,
8393
autoEnrolled: false,
94+
source: null,
95+
createdByEmail: 'creator@sourcebot.dev',
8496
createdAt: '2026-06-18T00:00:00.000Z',
8597
updatedAt: '2026-06-18T00:00:00.000Z',
8698
};
@@ -93,7 +105,7 @@ describe('WorkspaceAskAgentPage', () => {
93105
await screen.findByText('Ask Sourcebot connectors are unavailable');
94106

95107
expect(screen.queryByText('Review')).toBeNull();
96-
expect(screen.queryByText('Manage shared skills available to everyone in your workspace.')).toBeNull();
108+
expect(screen.queryByText('Available to everyone in your workspace.')).toBeNull();
97109
});
98110

99111
test('disables shared skill delete while a flag update is pending', async () => {
@@ -105,6 +117,8 @@ describe('WorkspaceAskAgentPage', () => {
105117
description: 'Review risky changes.',
106118
enabled: true,
107119
autoEnrolled: false,
120+
source: null,
121+
createdByEmail: 'creator@sourcebot.dev',
108122
createdAt: '2026-06-18T00:00:00.000Z',
109123
updatedAt: '2026-06-18T00:00:00.000Z',
110124
};
@@ -133,6 +147,8 @@ describe('WorkspaceAskAgentPage', () => {
133147
description: 'Review risky changes.',
134148
enabled: true,
135149
autoEnrolled: false,
150+
source: null,
151+
createdByEmail: 'creator@sourcebot.dev',
136152
createdAt: '2026-06-18T00:00:00.000Z',
137153
updatedAt: '2026-06-18T00:00:00.000Z',
138154
};
@@ -144,6 +160,8 @@ describe('WorkspaceAskAgentPage', () => {
144160
description: 'Review risky changes.',
145161
enabled: true,
146162
autoEnrolled: false,
163+
source: null,
164+
createdByEmail: 'creator@sourcebot.dev',
147165
createdAt: '2026-06-18T00:00:00.000Z',
148166
updatedAt: '2026-06-18T00:00:00.000Z',
149167
};
@@ -185,6 +203,8 @@ describe('WorkspaceAskAgentPage', () => {
185203
description: 'Review risky changes.',
186204
enabled: true,
187205
autoEnrolled: false,
206+
source: null,
207+
createdByEmail: 'creator@sourcebot.dev',
188208
createdAt: '2026-06-18T00:00:00.000Z',
189209
updatedAt: '2026-06-18T00:00:00.000Z',
190210
};
@@ -196,6 +216,8 @@ describe('WorkspaceAskAgentPage', () => {
196216
description: 'Review risky changes.',
197217
enabled: true,
198218
autoEnrolled: false,
219+
source: null,
220+
createdByEmail: 'creator@sourcebot.dev',
199221
createdAt: '2026-06-18T00:00:00.000Z',
200222
updatedAt: '2026-06-18T00:00:00.000Z',
201223
};

0 commit comments

Comments
 (0)