From c9e7567aa86d993e861d849fe8107c53cdcad313 Mon Sep 17 00:00:00 2001 From: Tracy Johnson Date: Tue, 25 Aug 2026 14:57:36 +0000 Subject: [PATCH 1/8] fix(site/src/pages/AgentsPage): use outlined empty state and kebab menu for personal skills --- ...SettingsPersonalSkillsPageView.stories.tsx | 54 +++++++- .../AgentSettingsPersonalSkillsPageView.tsx | 118 ++++++++++-------- 2 files changed, 119 insertions(+), 53 deletions(-) diff --git a/site/src/pages/AgentsPage/AgentSettingsPersonalSkillsPageView.stories.tsx b/site/src/pages/AgentsPage/AgentSettingsPersonalSkillsPageView.stories.tsx index de1eb4ad987d6..f476ef996deb3 100644 --- a/site/src/pages/AgentsPage/AgentSettingsPersonalSkillsPageView.stories.tsx +++ b/site/src/pages/AgentsPage/AgentSettingsPersonalSkillsPageView.stories.tsx @@ -58,6 +58,13 @@ export const DownloadingSkill: Story = { args: { downloadingSkillName: "review-sql", }, + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + const row = canvas.getByRole("row", { name: /review-sql/ }); + await expect( + within(row).getByRole("button", { name: "Open menu" }), + ).toBeDisabled(); + }, }; export const ExportingAll: Story = { @@ -69,9 +76,14 @@ export const ExportingAll: Story = { export const DownloadsSkill: Story = { play: async ({ canvasElement, args }) => { const canvas = within(canvasElement); + const body = within(canvasElement.ownerDocument.body); const row = canvas.getByRole("row", { name: /review-sql/ }); await userEvent.click( - within(row).getByRole("button", { name: "Download" }), + within(row).getByRole("button", { name: "Open menu" }), + ); + const menu = await body.findByRole("menu"); + await userEvent.click( + within(menu).getByRole("menuitem", { name: "Download" }), ); await waitFor(() => { @@ -82,6 +94,46 @@ export const DownloadsSkill: Story = { }, }; +export const EditsSkillFromMenu: Story = { + play: async ({ canvasElement, args }) => { + const canvas = within(canvasElement); + const body = within(canvasElement.ownerDocument.body); + const row = canvas.getByRole("row", { name: /write-release-notes/ }); + await userEvent.click( + within(row).getByRole("button", { name: "Open menu" }), + ); + const menu = await body.findByRole("menu"); + await userEvent.click(within(menu).getByRole("menuitem", { name: "Edit" })); + + await waitFor(() => { + expect(args.onEdit).toHaveBeenCalledWith("write-release-notes"); + expect(args.onDelete).not.toHaveBeenCalled(); + }); + }, +}; + +export const DeletesSkillFromMenu: Story = { + play: async ({ canvasElement, args }) => { + const canvas = within(canvasElement); + const body = within(canvasElement.ownerDocument.body); + const row = canvas.getByRole("row", { name: /review-sql/ }); + await userEvent.click( + within(row).getByRole("button", { name: "Open menu" }), + ); + const menu = await body.findByRole("menu"); + await userEvent.click( + within(menu).getByRole("menuitem", { name: /Delete/ }), + ); + + await waitFor(() => { + expect(args.onDelete).toHaveBeenCalledWith( + expect.objectContaining({ name: "review-sql" }), + ); + expect(args.onEdit).not.toHaveBeenCalled(); + }); + }, +}; + export const ExportsAllSkills: Story = { play: async ({ canvasElement, args }) => { const canvas = within(canvasElement); diff --git a/site/src/pages/AgentsPage/AgentSettingsPersonalSkillsPageView.tsx b/site/src/pages/AgentsPage/AgentSettingsPersonalSkillsPageView.tsx index 62ebaf71585cb..cd7d132f04abf 100644 --- a/site/src/pages/AgentsPage/AgentSettingsPersonalSkillsPageView.tsx +++ b/site/src/pages/AgentsPage/AgentSettingsPersonalSkillsPageView.tsx @@ -1,3 +1,4 @@ +import { EllipsisVerticalIcon } from "lucide-react"; import type { FC } from "react"; import type { UserSkillMetadata } from "#/api/typesGenerated"; import { Alert, AlertDescription } from "#/components/Alert/Alert"; @@ -12,7 +13,12 @@ import { DialogHeader, DialogTitle, } from "#/components/Dialog/Dialog"; -import { EmptyState } from "#/components/EmptyState/EmptyState"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from "#/components/DropdownMenu/DropdownMenu"; import { Loader } from "#/components/Loader/Loader"; import { Spinner } from "#/components/Spinner/Spinner"; import { @@ -23,6 +29,7 @@ import { TableHeader, TableRow, } from "#/components/Table/Table"; +import { TableEmpty } from "#/components/TableEmpty/TableEmpty"; import { formatDate } from "#/utils/time"; import type { PersonalSkillErrorDisplay } from "./components/PersonalSkillEditor"; import { PersonalSkillEditor } from "./components/PersonalSkillEditor"; @@ -272,12 +279,6 @@ export const AgentSettingsPersonalSkillsPageView: FC< ) : isLoading ? ( - ) : skills.length === 0 ? ( - ) : ( @@ -285,54 +286,67 @@ export const AgentSettingsPersonalSkillsPageView: FC< Name Description Updated - Actions + + Actions + - {skills.map((skill) => ( - - - {skill.name} - - - {skill.description || ( - - No description - - )} - - {formatUpdatedAt(skill.updated_at)} - -
- - - -
-
-
- ))} + {skills.length === 0 ? ( + + ) : ( + skills.map((skill) => ( + + + {skill.name} + + + {skill.description || ( + + No description + + )} + + {formatUpdatedAt(skill.updated_at)} + + + + + + + onDownload(skill)}> + Download + + onEdit(skill.name)}> + Edit + + onDelete(skill)} + > + Delete… + + + + + + )) + )}
)} From af73e262d9bafe629a2c33f6c5711057862d78d0 Mon Sep 17 00:00:00 2001 From: Tracy Johnson Date: Tue, 25 Aug 2026 15:12:38 +0000 Subject: [PATCH 2/8] fix(site/src/pages/AgentsPage): consolidate personal skills menu stories --- ...SettingsPersonalSkillsPageView.stories.tsx | 50 ++++++------------- 1 file changed, 16 insertions(+), 34 deletions(-) diff --git a/site/src/pages/AgentsPage/AgentSettingsPersonalSkillsPageView.stories.tsx b/site/src/pages/AgentsPage/AgentSettingsPersonalSkillsPageView.stories.tsx index f476ef996deb3..31b7891ab56a1 100644 --- a/site/src/pages/AgentsPage/AgentSettingsPersonalSkillsPageView.stories.tsx +++ b/site/src/pages/AgentsPage/AgentSettingsPersonalSkillsPageView.stories.tsx @@ -73,63 +73,45 @@ export const ExportingAll: Story = { }, }; -export const DownloadsSkill: Story = { +export const RowMenuActions: Story = { play: async ({ canvasElement, args }) => { const canvas = within(canvasElement); const body = within(canvasElement.ownerDocument.body); const row = canvas.getByRole("row", { name: /review-sql/ }); + const trigger = within(row).getByRole("button", { name: "Open menu" }); + + await userEvent.click(trigger); await userEvent.click( - within(row).getByRole("button", { name: "Open menu" }), - ); - const menu = await body.findByRole("menu"); - await userEvent.click( - within(menu).getByRole("menuitem", { name: "Download" }), + within(await body.findByRole("menu")).getByRole("menuitem", { + name: "Download", + }), ); - await waitFor(() => { expect(args.onDownload).toHaveBeenCalledWith( expect.objectContaining({ name: "review-sql" }), ); }); - }, -}; -export const EditsSkillFromMenu: Story = { - play: async ({ canvasElement, args }) => { - const canvas = within(canvasElement); - const body = within(canvasElement.ownerDocument.body); - const row = canvas.getByRole("row", { name: /write-release-notes/ }); + await userEvent.click(trigger); await userEvent.click( - within(row).getByRole("button", { name: "Open menu" }), + within(await body.findByRole("menu")).getByRole("menuitem", { + name: "Edit", + }), ); - const menu = await body.findByRole("menu"); - await userEvent.click(within(menu).getByRole("menuitem", { name: "Edit" })); - await waitFor(() => { - expect(args.onEdit).toHaveBeenCalledWith("write-release-notes"); - expect(args.onDelete).not.toHaveBeenCalled(); + expect(args.onEdit).toHaveBeenCalledWith("review-sql"); }); - }, -}; -export const DeletesSkillFromMenu: Story = { - play: async ({ canvasElement, args }) => { - const canvas = within(canvasElement); - const body = within(canvasElement.ownerDocument.body); - const row = canvas.getByRole("row", { name: /review-sql/ }); + await userEvent.click(trigger); await userEvent.click( - within(row).getByRole("button", { name: "Open menu" }), + within(await body.findByRole("menu")).getByRole("menuitem", { + name: /Delete/, + }), ); - const menu = await body.findByRole("menu"); - await userEvent.click( - within(menu).getByRole("menuitem", { name: /Delete/ }), - ); - await waitFor(() => { expect(args.onDelete).toHaveBeenCalledWith( expect.objectContaining({ name: "review-sql" }), ); - expect(args.onEdit).not.toHaveBeenCalled(); }); }, }; From 521b6bd3b2a5847371490d5d6906ffbf7eaa5a83 Mon Sep 17 00:00:00 2001 From: Tracy Johnson Date: Tue, 25 Aug 2026 16:15:34 +0000 Subject: [PATCH 3/8] fix(site/src/pages/AgentsPage): keep personal skills table outline in all states --- .../AgentSettingsPersonalSkillsPageView.tsx | 175 +++++++++--------- 1 file changed, 88 insertions(+), 87 deletions(-) diff --git a/site/src/pages/AgentsPage/AgentSettingsPersonalSkillsPageView.tsx b/site/src/pages/AgentsPage/AgentSettingsPersonalSkillsPageView.tsx index cd7d132f04abf..f3f61d77a9c23 100644 --- a/site/src/pages/AgentsPage/AgentSettingsPersonalSkillsPageView.tsx +++ b/site/src/pages/AgentsPage/AgentSettingsPersonalSkillsPageView.tsx @@ -19,7 +19,6 @@ import { DropdownMenuItem, DropdownMenuTrigger, } from "#/components/DropdownMenu/DropdownMenu"; -import { Loader } from "#/components/Loader/Loader"; import { Spinner } from "#/components/Spinner/Spinner"; import { Table, @@ -30,6 +29,7 @@ import { TableRow, } from "#/components/Table/Table"; import { TableEmpty } from "#/components/TableEmpty/TableEmpty"; +import { TableLoader } from "#/components/TableLoader/TableLoader"; import { formatDate } from "#/utils/time"; import type { PersonalSkillErrorDisplay } from "./components/PersonalSkillEditor"; import { PersonalSkillEditor } from "./components/PersonalSkillEditor"; @@ -264,92 +264,93 @@ export const AgentSettingsPersonalSkillsPageView: FC< )} - {error ? ( -
- - -
- ) : isLoading ? ( - - ) : ( - - - - Name - Description - Updated - - Actions - - - - - {skills.length === 0 ? ( - - ) : ( - skills.map((skill) => ( - - - {skill.name} - - - {skill.description || ( - - No description - - )} - - {formatUpdatedAt(skill.updated_at)} - - - - - - - onDownload(skill)}> - Download - - onEdit(skill.name)}> - Edit - - onDelete(skill)} - > - Delete… - - - - - - )) - )} - -
- )} + {Boolean(error) && } + + + + + Name + Description + Updated + + Actions + + + + + {isLoading ? ( + + ) : error ? ( + + {isRetrying && } + Retry + + } + /> + ) : skills.length === 0 ? ( + + ) : ( + skills.map((skill) => ( + + + {skill.name} + + + {skill.description || ( + + No description + + )} + + {formatUpdatedAt(skill.updated_at)} + + + + + + + onDownload(skill)}> + Download + + onEdit(skill.name)}> + Edit + + onDelete(skill)} + > + Delete… + + + + + + )) + )} + +
{editorState?.mode === "create" && ( Date: Tue, 25 Aug 2026 16:23:08 +0000 Subject: [PATCH 4/8] fix(site/src/pages/AgentsPage): restore Loader import for edit skill dialog --- .../src/pages/AgentsPage/AgentSettingsPersonalSkillsPageView.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/site/src/pages/AgentsPage/AgentSettingsPersonalSkillsPageView.tsx b/site/src/pages/AgentsPage/AgentSettingsPersonalSkillsPageView.tsx index f3f61d77a9c23..6578c1373b452 100644 --- a/site/src/pages/AgentsPage/AgentSettingsPersonalSkillsPageView.tsx +++ b/site/src/pages/AgentsPage/AgentSettingsPersonalSkillsPageView.tsx @@ -19,6 +19,7 @@ import { DropdownMenuItem, DropdownMenuTrigger, } from "#/components/DropdownMenu/DropdownMenu"; +import { Loader } from "#/components/Loader/Loader"; import { Spinner } from "#/components/Spinner/Spinner"; import { Table, From d6de15bc5081c8bf02c43672d6fe9225240afcfa Mon Sep 17 00:00:00 2001 From: Tracy Johnson Date: Tue, 25 Aug 2026 16:24:33 +0000 Subject: [PATCH 5/8] fix(site/src/pages/AgentsPage): separate destructive action in personal skills menu --- .../pages/AgentsPage/AgentSettingsPersonalSkillsPageView.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/site/src/pages/AgentsPage/AgentSettingsPersonalSkillsPageView.tsx b/site/src/pages/AgentsPage/AgentSettingsPersonalSkillsPageView.tsx index 6578c1373b452..21db3a288336e 100644 --- a/site/src/pages/AgentsPage/AgentSettingsPersonalSkillsPageView.tsx +++ b/site/src/pages/AgentsPage/AgentSettingsPersonalSkillsPageView.tsx @@ -17,6 +17,7 @@ import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, + DropdownMenuSeparator, DropdownMenuTrigger, } from "#/components/DropdownMenu/DropdownMenu"; import { Loader } from "#/components/Loader/Loader"; @@ -338,6 +339,7 @@ export const AgentSettingsPersonalSkillsPageView: FC< onEdit(skill.name)}> Edit + onDelete(skill)} From 879560db0de93f35127749769ca9a4b302ccac28 Mon Sep 17 00:00:00 2001 From: Tracy Johnson Date: Tue, 25 Aug 2026 16:42:43 +0000 Subject: [PATCH 6/8] fix(site/src/pages/AgentsPage): align personal skills buttons and cell text with table conventions --- .../AgentSettingsPersonalSkillsPageView.tsx | 22 ++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/site/src/pages/AgentsPage/AgentSettingsPersonalSkillsPageView.tsx b/site/src/pages/AgentsPage/AgentSettingsPersonalSkillsPageView.tsx index 21db3a288336e..63ff719e81047 100644 --- a/site/src/pages/AgentsPage/AgentSettingsPersonalSkillsPageView.tsx +++ b/site/src/pages/AgentsPage/AgentSettingsPersonalSkillsPageView.tsx @@ -1,4 +1,4 @@ -import { EllipsisVerticalIcon } from "lucide-react"; +import { EllipsisVerticalIcon, FolderDownIcon, PlusIcon } from "lucide-react"; import type { FC } from "react"; import type { UserSkillMetadata } from "#/api/typesGenerated"; import { Alert, AlertDescription } from "#/components/Alert/Alert"; @@ -230,19 +230,27 @@ export const AgentSettingsPersonalSkillsPageView: FC< }) => { const isAtLimit = skills.length >= PERSONAL_SKILLS_MAX_PER_USER; const addSkillAction = ( - ); const headerActions = (
{addSkillAction} @@ -305,12 +313,10 @@ export const AgentSettingsPersonalSkillsPageView: FC< ) : ( skills.map((skill) => ( - - {skill.name} - + {skill.name} {skill.description || ( - + No description )} From eee91e9cfece055a8e4be7e82fe8df40dd70da0b Mon Sep 17 00:00:00 2001 From: Tracy Johnson Date: Tue, 25 Aug 2026 16:54:15 +0000 Subject: [PATCH 7/8] fix(site/src/pages/AgentsPage): drop icon from export all button --- .../AgentsPage/AgentSettingsPersonalSkillsPageView.tsx | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/site/src/pages/AgentsPage/AgentSettingsPersonalSkillsPageView.tsx b/site/src/pages/AgentsPage/AgentSettingsPersonalSkillsPageView.tsx index 63ff719e81047..0fc69d445bc7d 100644 --- a/site/src/pages/AgentsPage/AgentSettingsPersonalSkillsPageView.tsx +++ b/site/src/pages/AgentsPage/AgentSettingsPersonalSkillsPageView.tsx @@ -1,4 +1,4 @@ -import { EllipsisVerticalIcon, FolderDownIcon, PlusIcon } from "lucide-react"; +import { EllipsisVerticalIcon, PlusIcon } from "lucide-react"; import type { FC } from "react"; import type { UserSkillMetadata } from "#/api/typesGenerated"; import { Alert, AlertDescription } from "#/components/Alert/Alert"; @@ -246,11 +246,7 @@ export const AgentSettingsPersonalSkillsPageView: FC< onClick={onExportAll} disabled={isLoading || isExportingAll || skills.length === 0} > - {isExportingAll ? ( - - ) : ( - - )} + {isExportingAll && } Export all {addSkillAction} From c62b02b34967c11e861c7a8306ab0c5077dca236 Mon Sep 17 00:00:00 2001 From: Tracy Johnson Date: Tue, 25 Aug 2026 17:18:39 +0000 Subject: [PATCH 8/8] fix(site/src/pages/AgentsPage): keep cached skills visible on refetch error and row menu usable during download --- ...SettingsPersonalSkillsPageView.stories.tsx | 25 +++++++++++++++++-- .../AgentSettingsPersonalSkillsPageView.tsx | 8 +++--- 2 files changed, 28 insertions(+), 5 deletions(-) diff --git a/site/src/pages/AgentsPage/AgentSettingsPersonalSkillsPageView.stories.tsx b/site/src/pages/AgentsPage/AgentSettingsPersonalSkillsPageView.stories.tsx index 31b7891ab56a1..979ff10051ff4 100644 --- a/site/src/pages/AgentsPage/AgentSettingsPersonalSkillsPageView.stories.tsx +++ b/site/src/pages/AgentsPage/AgentSettingsPersonalSkillsPageView.stories.tsx @@ -60,10 +60,18 @@ export const DownloadingSkill: Story = { }, play: async ({ canvasElement }) => { const canvas = within(canvasElement); + const body = within(canvasElement.ownerDocument.body); const row = canvas.getByRole("row", { name: /review-sql/ }); - await expect( + await userEvent.click( within(row).getByRole("button", { name: "Open menu" }), - ).toBeDisabled(); + ); + const menu = await body.findByRole("menu"); + await expect( + within(menu).getByRole("menuitem", { name: "Download" }), + ).toHaveAttribute("aria-disabled", "true"); + await expect( + within(menu).getByRole("menuitem", { name: "Edit" }), + ).not.toHaveAttribute("aria-disabled"); }, }; @@ -147,6 +155,19 @@ export const ListError: Story = { }, }; +export const RefetchErrorKeepsRows: Story = { + args: { + error: new Error("Failed to load personal skills."), + }, + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + await expect(canvas.getByRole("row", { name: /review-sql/ })).toBeVisible(); + await expect( + canvas.getByText("Failed to load personal skills."), + ).toBeVisible(); + }, +}; + export const CreateDialogOpen: Story = { args: { editorState: { diff --git a/site/src/pages/AgentsPage/AgentSettingsPersonalSkillsPageView.tsx b/site/src/pages/AgentsPage/AgentSettingsPersonalSkillsPageView.tsx index 0fc69d445bc7d..33ec027a0ee68 100644 --- a/site/src/pages/AgentsPage/AgentSettingsPersonalSkillsPageView.tsx +++ b/site/src/pages/AgentsPage/AgentSettingsPersonalSkillsPageView.tsx @@ -286,7 +286,7 @@ export const AgentSettingsPersonalSkillsPageView: FC< {isLoading ? ( - ) : error ? ( + ) : skills.length === 0 && error ? ( {downloadingSkillName === skill.name ? ( @@ -335,7 +334,10 @@ export const AgentSettingsPersonalSkillsPageView: FC< - onDownload(skill)}> + onDownload(skill)} + disabled={downloadingSkillName === skill.name} + > Download onEdit(skill.name)}>