From 27414788f7864ceaee500e678196ef1a7fe4c932 Mon Sep 17 00:00:00 2001 From: Susana Ferreira Date: Mon, 10 Aug 2026 16:08:06 +0100 Subject: [PATCH] chore: use 'AI spend' and 'approximate' in budget UI (#27977) The "AI budget" column shows spend against the configured limit, not the limit itself, so "AI spend" describes what it actually displays. Renamed in both the groups table and the group members table. Also replaces "estimated" with "approximate" in the surrounding spend copy, which reads more plainly as a signal that the amount isn't exact. The user dropdown now keeps the date range on a single line, so the label wraps above it instead of the dates splitting across two lines. Before: Screenshot 2026-08-10 at 12 55 55 After: Screenshot 2026-08-10 at 12 57 09 Related to internal slack thread: https://codercom.slack.com/archives/C096PFVBZKN/p1786051712464009 > [!NOTE] > Initially generated by Claude Opus 5, modified and reviewed by @ssncferreira --- .../UserDropdown/UserDropdown.stories.tsx | 22 +++++++++---------- .../UserDropdown/UserDropdownAISpend.tsx | 5 ++++- .../GroupMemberBudgetCells.stories.tsx | 2 +- .../GroupsPage/GroupMemberBudgetCells.tsx | 2 +- .../src/pages/GroupsPage/GroupMembersPage.tsx | 4 ++-- .../pages/GroupsPage/GroupPage.stories.tsx | 12 +++++----- .../GroupsPage/GroupsPageView.stories.tsx | 6 ++--- site/src/pages/GroupsPage/GroupsPageView.tsx | 6 ++--- 8 files changed, 31 insertions(+), 28 deletions(-) diff --git a/site/src/modules/dashboard/Navbar/UserDropdown/UserDropdown.stories.tsx b/site/src/modules/dashboard/Navbar/UserDropdown/UserDropdown.stories.tsx index 4577a78173..58c518a998 100644 --- a/site/src/modules/dashboard/Navbar/UserDropdown/UserDropdown.stories.tsx +++ b/site/src/modules/dashboard/Navbar/UserDropdown/UserDropdown.stories.tsx @@ -18,7 +18,7 @@ const mockAISpend: UserAISpendStatus = { period_end: "2026-07-01T00:00:00Z", }; -const spendPeriodLabel = "Estimated AI spend: June 1 - July 1, 2026"; +const spendPeriodLabel = "Approximate AI spend June 1 - July 1, 2026"; const aiCostControl: { features: FeatureName[] } = { features: ["aibridge"], @@ -71,10 +71,10 @@ export const WithAISpend: Story = { }, play: async ({ canvasElement, step }) => { await step("shows AI spend", async () => { - const menu = await openDropdown(canvasElement); + await openDropdown(canvasElement); await waitFor(() => { expect(document.body).toHaveTextContent("$819 / $1,200 USD"); - expect(menu.getByText(spendPeriodLabel)).toBeVisible(); + expect(document.body).toHaveTextContent(spendPeriodLabel); }); expect( screen.getByRole("progressbar", { name: "AI spend usage" }), @@ -95,10 +95,10 @@ export const AISpendWarning: Story = { }, play: async ({ canvasElement, step }) => { await step("shows the warning marker near the limit", async () => { - const menu = await openDropdown(canvasElement); + await openDropdown(canvasElement); await waitFor(() => { expect(document.body).toHaveTextContent("$1,080 / $1,200 USD"); - expect(menu.getByText(spendPeriodLabel)).toBeVisible(); + expect(document.body).toHaveTextContent(spendPeriodLabel); }); expect( screen.getByRole("progressbar", { name: "AI spend usage" }), @@ -142,10 +142,10 @@ export const AISpendExceeded: Story = { }, play: async ({ canvasElement, step }) => { await step("shows the exceeded marker at the limit", async () => { - const menu = await openDropdown(canvasElement); + await openDropdown(canvasElement); await waitFor(() => { expect(document.body).toHaveTextContent("$1,500 / $1,200 USD"); - expect(menu.getByText(spendPeriodLabel)).toBeVisible(); + expect(document.body).toHaveTextContent(spendPeriodLabel); }); expect( screen.getByRole("progressbar", { name: "AI spend usage" }), @@ -163,10 +163,10 @@ export const AISpendUnlimited: Story = { }, play: async ({ canvasElement, step }) => { await step("shows unlimited spend without a bar", async () => { - const menu = await openDropdown(canvasElement); + await openDropdown(canvasElement); await waitFor(() => { expect(document.body).toHaveTextContent("$819 / Unlimited USD"); - expect(menu.getByText(spendPeriodLabel)).toBeVisible(); + expect(document.body).toHaveTextContent(spendPeriodLabel); }); expect( screen.queryByRole("progressbar", { name: "AI spend usage" }), @@ -274,7 +274,7 @@ export const AISpendHiddenOnInvalidData: Story = { play: async ({ canvasElement, step }) => { await step("hides AI spend on invalid data", async () => { await openDropdown(canvasElement); - expect(screen.queryByText(spendPeriodLabel)).not.toBeInTheDocument(); + expect(document.body).not.toHaveTextContent(spendPeriodLabel); }); }, }; @@ -295,7 +295,7 @@ export const AISpendHiddenOnNegativeLimit: Story = { play: async ({ canvasElement, step }) => { await step("hides AI spend on a negative limit", async () => { await openDropdown(canvasElement); - expect(screen.queryByText(spendPeriodLabel)).not.toBeInTheDocument(); + expect(document.body).not.toHaveTextContent(spendPeriodLabel); }); }, }; diff --git a/site/src/modules/dashboard/Navbar/UserDropdown/UserDropdownAISpend.tsx b/site/src/modules/dashboard/Navbar/UserDropdown/UserDropdownAISpend.tsx index 8060e5efd1..968d31c053 100644 --- a/site/src/modules/dashboard/Navbar/UserDropdown/UserDropdownAISpend.tsx +++ b/site/src/modules/dashboard/Navbar/UserDropdown/UserDropdownAISpend.tsx @@ -39,7 +39,10 @@ export const UserDropdownAISpend: FC = ({ /> )}
- Estimated AI spend: {formatSpendPeriodLabel(periodStart, periodEnd)} + Approximate AI spend{" "} + + {formatSpendPeriodLabel(periodStart, periodEnd)} +
); diff --git a/site/src/pages/GroupsPage/GroupMemberBudgetCells.stories.tsx b/site/src/pages/GroupsPage/GroupMemberBudgetCells.stories.tsx index 758c8b6692..2ca857b8e7 100644 --- a/site/src/pages/GroupsPage/GroupMemberBudgetCells.stories.tsx +++ b/site/src/pages/GroupsPage/GroupMemberBudgetCells.stories.tsx @@ -45,7 +45,7 @@ const meta: Meta = { - AI budget + AI spend Budget group diff --git a/site/src/pages/GroupsPage/GroupMemberBudgetCells.tsx b/site/src/pages/GroupsPage/GroupMemberBudgetCells.tsx index eb69220616..a449b96612 100644 --- a/site/src/pages/GroupsPage/GroupMemberBudgetCells.tsx +++ b/site/src/pages/GroupsPage/GroupMemberBudgetCells.tsx @@ -17,7 +17,7 @@ const OTHER_ORG_MESSAGE = "This user's AI budget is managed by a group in another organization and isn't visible here."; /** - * The AI budget and Budget group cells for a group member. Spend is scoped to + * The AI spend and Budget group cells for a group member. Spend is scoped to * the viewed group; the limit comes from the member's effective group. */ export const GroupMemberBudgetCells: FC<{ diff --git a/site/src/pages/GroupsPage/GroupMembersPage.tsx b/site/src/pages/GroupsPage/GroupMembersPage.tsx index 33ff7b2237..48727a29ca 100644 --- a/site/src/pages/GroupsPage/GroupMembersPage.tsx +++ b/site/src/pages/GroupsPage/GroupMembersPage.tsx @@ -101,7 +101,7 @@ const GroupMembersPage: FC = () => { }), ); const aiBudgetNote = [ - "Estimated monthly AI spend for this user.", + "Approximate monthly AI spend for this user.", // Spend resets at period_end, rendered in the viewer's local time. aiSpend && `Resets ${dayjs(aiSpend.period_end).format("MMM D, YYYY h:mm A")}.`, @@ -141,7 +141,7 @@ const GroupMembersPage: FC = () => { <>
- AI budget + AI spend {membersSpendQuery.isError ? ( { const canvas = within(canvasElement); - await expect(await canvas.findByText("AI budget")).toBeInTheDocument(); + await expect(await canvas.findByText("AI spend")).toBeInTheDocument(); await expect(await canvas.findByText("Budget group")).toBeInTheDocument(); await expect( await canvas.findByText("AI budget period: June 1 - July 1, 2026"), @@ -358,13 +358,13 @@ export const WithMemberAIBudget: Story = { const body = within(document.body); await userEvent.click( - within(canvas.getByText("AI budget")).getByRole("button", { + within(canvas.getByText("AI spend")).getByRole("button", { name: "More info", }), ); await expect( await body.findByText( - /^Estimated monthly AI spend for this user\. Resets .*The group's default limit is \$7,000 per member\.$/, + /^Approximate monthly AI spend for this user\. Resets .*The group's default limit is \$7,000 per member\.$/, ), ).toBeInTheDocument(); await userEvent.click( @@ -391,7 +391,7 @@ export const WithoutMemberAIBudgetColumn: Story = { play: async ({ canvasElement }) => { const canvas = within(canvasElement); await canvas.findByRole("table", { name: "Group members" }); - expect(canvas.queryByText("AI budget")).not.toBeInTheDocument(); + expect(canvas.queryByText("AI spend")).not.toBeInTheDocument(); expect(canvas.queryByText("Budget group")).not.toBeInTheDocument(); expect(canvas.queryByText(/AI budget period/)).not.toBeInTheDocument(); }, @@ -428,13 +428,13 @@ export const AIBudgetActionDisabledForOtherGroup: Story = { // Without a group default budget, the header note ends at the reset date. await userEvent.click( - within(canvas.getByText("AI budget")).getByRole("button", { + within(canvas.getByText("AI spend")).getByRole("button", { name: "More info", }), ); await expect( await body.findByText( - /^Estimated monthly AI spend for this user\. Resets .*\.$/, + /^Approximate monthly AI spend for this user\. Resets .*\.$/, ), ).toBeInTheDocument(); await userEvent.keyboard("{Escape}"); diff --git a/site/src/pages/GroupsPage/GroupsPageView.stories.tsx b/site/src/pages/GroupsPage/GroupsPageView.stories.tsx index adb97a9b13..9d74dddaab 100644 --- a/site/src/pages/GroupsPage/GroupsPageView.stories.tsx +++ b/site/src/pages/GroupsPage/GroupsPageView.stories.tsx @@ -171,7 +171,7 @@ export const WithAIBudgetsLoading: Story = { }, }; -// Spend still loading: every AI budget cell falls back to an em dash. +// Spend still loading: every AI spend cell falls back to an em dash. export const WithAIBudgetsSpendLoading: Story = { args: { groups: [aiGroup("ai-loading", "Spend loading")], @@ -223,7 +223,7 @@ export const WithAIBudgetsSpendUnavailable: Story = { }, }; -// AI Bridge hidden: no AI budget column. +// AI Bridge hidden: no AI spend column. export const WithoutAIBudgetColumn: Story = { args: { groups: [aiGroup("ai-hidden", "No AI column")], @@ -233,7 +233,7 @@ export const WithoutAIBudgetColumn: Story = { }, play: async ({ canvasElement }) => { const canvas = within(canvasElement); - expect(canvas.queryByText("AI budget")).not.toBeInTheDocument(); + expect(canvas.queryByText("AI spend")).not.toBeInTheDocument(); }, }; diff --git a/site/src/pages/GroupsPage/GroupsPageView.tsx b/site/src/pages/GroupsPage/GroupsPageView.tsx index 6ae73a1ff0..2a2bbe3ef5 100644 --- a/site/src/pages/GroupsPage/GroupsPageView.tsx +++ b/site/src/pages/GroupsPage/GroupsPageView.tsx @@ -92,7 +92,7 @@ export const GroupsPageView: FC = ({ {showAIBudget && (
- AI budget + AI spend {spendError ? ( = ({ - Estimated AI spend compared to the group's AI budget for - the active period. + Approximate AI spend compared to the group's AI budget + for the active period. } />