refactor(site/src/pages/AgentsPage/components): name the project actions gate and cover long names

This commit is contained in:
Jaayden Halko
2026-10-15 22:09:17 +00:00
parent 9f984f9c82
commit 6f2cde9aa0
6 changed files with 36 additions and 24 deletions
@@ -188,7 +188,8 @@ type AgentChatInputProps = {
chatProjectId?: string;
isChatProjectPending?: boolean;
isWorkspaceLoading?: boolean;
// Offers the chat project's action skills above the composer.
// Opts in to project action skills above the composer, which also need
// the chat-projects experiment and a composer the user can send from.
showProjectActions?: boolean;
// Queued user messages rendered above the textarea.
queuedMessages?: readonly ChatQueuedMessage[];
@@ -1318,6 +1319,13 @@ export const AgentChatInput: React.FC<AgentChatInputProps> = ({
const draftOccupiesSlot =
hasSendableContent || hasActiveUploads || speech.isRecording;
const editingHoldsStop = isEditingHistoryMessage && !speech.isRecording;
const canShowProjectActions =
showProjectActions &&
experiments.includes("chat-projects") &&
!isReadOnly &&
!isReadOnlyView &&
hasModelOptions &&
!aiGatewayDisabled;
const showStopButton =
!isReadOnlyView &&
isStreaming &&
@@ -1351,22 +1359,13 @@ export const AgentChatInput: React.FC<AgentChatInputProps> = ({
isEditingHistoryMessage && "pt-1",
)}
>
{showProjectActions &&
experiments.includes("chat-projects") &&
chatOrganizationId &&
chatProjectId &&
!isReadOnly &&
!isReadOnlyView &&
hasModelOptions &&
!aiGatewayDisabled && (
<ProjectSkillActions
organizationId={chatOrganizationId}
projectId={chatProjectId}
onInvoke={(name) =>
internalRef.current?.insertSkill("project", name)
}
/>
)}
{canShowProjectActions && chatOrganizationId && chatProjectId && (
<ProjectSkillActions
organizationId={chatOrganizationId}
projectId={chatProjectId}
onInvoke={(name) => internalRef.current?.insertSkill("project", name)}
/>
)}
{queuedMessages.length > 0 && (
<QueuedMessagesList
messages={queuedMessages}
@@ -2088,6 +2088,10 @@ export const WithOrganizationAndProjectPickers: Story = {
},
};
const mockProjectActionSkills: TypesGen.SkillMetadata[] = MockSkills.map(
(skill) => ({ ...skill, action: true }),
);
export const ProjectComposerWithActions: Story = {
parameters: {
experiments: ["chat-projects"],
@@ -2095,7 +2099,7 @@ export const ProjectComposerWithActions: Story = {
...defaultQueries,
{
key: chatProjectSkillsKey(MockChatProject.id),
data: MockSkills.map((skill) => ({ ...skill, action: true })),
data: mockProjectActionSkills,
},
],
},
@@ -12,7 +12,10 @@ const mockActionSkills: SkillMetadata[] = [
description: "Deploy the current branch to the staging cluster.",
},
{ name: "lint", description: "Run the linters and fix what they report." },
{ name: "release", description: "Draft release notes for the next version." },
{
name: "release-notes-for-the-next-version-with-every-merged-change",
description: "Draft release notes for the next version.",
},
{ name: "review", description: "Review changed files and suggest fixes." },
{ name: "triage", description: "" },
].map((skill) => ({
@@ -28,8 +28,10 @@ const mockSkills: SkillMetadata[] = [
{ ...MockSkill, id: "skill-deploy", name: "deploy", action: true },
];
const renderActions = (skills: SkillMetadata[]) => {
vi.spyOn(API.experimental, "getChatProjectSkills").mockResolvedValue(skills);
const renderActions = () => {
vi.spyOn(API.experimental, "getChatProjectSkills").mockResolvedValue(
mockSkills,
);
const onInvoke = vi.fn();
render(
<QueryClientProvider client={createTestQueryClient()}>
@@ -51,7 +53,7 @@ describe("ProjectSkillActions", () => {
});
it("offers the first three actions by name and the rest under More", async () => {
const { user, onInvoke } = renderActions(mockSkills);
const { user, onInvoke } = renderActions();
await user.click(await screen.findByRole("button", { name: "deploy" }));
await user.click(screen.getByRole("button", { name: "lint" }));
@@ -88,7 +88,9 @@ export const ProjectSkillActions: React.FC<ProjectSkillActionsProps> = ({
menuSelectionRef.current = skill.name;
}}
>
<span className="text-content-primary">{skill.name}</span>
<span className="max-w-full truncate text-content-primary">
{skill.name}
</span>
{skill.description && (
<span className="line-clamp-2 text-xs font-normal">
{skill.description}
@@ -225,7 +225,9 @@ export const SkillEditorForm: React.FC<SkillEditorFormProps> = ({
kind: "success",
title: "Imported SKILL.md",
detail: isCreate
? "Updated name, description, and body fields."
? showProjectSettings
? "Updated name, description, body, and automatic use."
: "Updated name, description, and body fields."
: "Updated description and body fields. Kept the existing name.",
});
};