From 7e23081c2fe32ff419b91c0312b87e75f85c5cfc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=82=B1=E3=82=A4=E3=83=A9?= Date: Fri, 22 Aug 2025 09:00:03 -0600 Subject: [PATCH 1/4] chore: fix vite types (#19477) --- .../DeploymentBanner/DeploymentBannerView.tsx | 108 ++++++++---------- site/tsconfig.json | 8 +- site/tsconfig.test.json | 5 - 3 files changed, 51 insertions(+), 70 deletions(-) delete mode 100644 site/tsconfig.test.json diff --git a/site/src/modules/dashboard/DeploymentBanner/DeploymentBannerView.tsx b/site/src/modules/dashboard/DeploymentBanner/DeploymentBannerView.tsx index 2c0732053fa20..4f9838e0255da 100644 --- a/site/src/modules/dashboard/DeploymentBanner/DeploymentBannerView.tsx +++ b/site/src/modules/dashboard/DeploymentBanner/DeploymentBannerView.tsx @@ -1,4 +1,3 @@ -import type { CSSInterpolation } from "@emotion/css/dist/declarations/src/create-instance"; import { css, type Interpolation, type Theme, useTheme } from "@emotion/react"; import Button from "@mui/material/Button"; import Link from "@mui/material/Link"; @@ -15,7 +14,6 @@ import { TerminalIcon } from "components/Icons/TerminalIcon"; import { VSCodeIcon } from "components/Icons/VSCodeIcon"; import { Stack } from "components/Stack/Stack"; import dayjs from "dayjs"; -import { type ClassName, useClassName } from "hooks/useClassName"; import { AppWindowIcon, CircleAlertIcon, @@ -53,7 +51,6 @@ export const DeploymentBannerView: FC = ({ fetchStats, }) => { const theme = useTheme(); - const summaryTooltip = useClassName(classNames.summaryTooltip, []); const aggregatedMinutes = useMemo(() => { if (!stats) { @@ -128,7 +125,10 @@ export const DeploymentBannerView: FC = ({ }} > 0 ? ( <> @@ -236,10 +236,10 @@ export const DeploymentBannerView: FC = ({
{typeof stats?.session_count.vscode === "undefined" ? "-" @@ -251,10 +251,10 @@ export const DeploymentBannerView: FC = ({
{typeof stats?.session_count.jetbrains === "undefined" ? "-" @@ -303,20 +303,20 @@ export const DeploymentBannerView: FC = ({ css={[ styles.value, css` - margin: 0; - padding: 0 8px; - height: unset; - min-height: unset; - font-size: unset; - color: unset; - border: 0; - min-width: unset; - font-family: inherit; + margin: 0; + padding: 0 8px; + height: unset; + min-height: unset; + font-size: unset; + color: unset; + border: 0; + min-width: unset; + font-family: inherit; - & svg { - margin-right: 4px; - } - `, + & svg { + margin-right: 4px; + } + `, ]} onClick={() => { if (fetchStats) { @@ -410,41 +410,27 @@ const getHealthErrors = (health: HealthcheckReport) => { return warnings; }; -const classNames = { - summaryTooltip: (css, theme) => css` - ${theme.typography.body2 as CSSInterpolation} - - margin: 0 0 4px 12px; - width: 400px; - padding: 16px; - color: ${theme.palette.text.primary}; - background-color: ${theme.palette.background.paper}; - border: 1px solid ${theme.palette.divider}; - pointer-events: none; - `, -} satisfies Record; - const styles = { statusBadge: (theme) => css` - display: flex; - align-items: center; - justify-content: center; - padding: 0 12px; - height: 100%; - color: ${theme.experimental.l1.text}; + display: flex; + align-items: center; + justify-content: center; + padding: 0 12px; + height: 100%; + color: ${theme.experimental.l1.text}; - & svg { - width: 16px; - height: 16px; - } - `, + & svg { + width: 16px; + height: 16px; + } + `, unhealthy: { backgroundColor: colors.red[700], }, group: css` - display: flex; - align-items: center; - `, + display: flex; + align-items: center; + `, category: (theme) => ({ marginRight: 16, color: theme.palette.text.primary, @@ -455,15 +441,15 @@ const styles = { color: theme.palette.text.secondary, }), value: css` - display: flex; - align-items: center; - gap: 4px; + display: flex; + align-items: center; + gap: 4px; - & svg { - width: 12px; - height: 12px; - } - `, + & svg { + width: 12px; + height: 12px; + } + `, separator: (theme) => ({ color: theme.palette.text.disabled, }), diff --git a/site/tsconfig.json b/site/tsconfig.json index 7e969d18c42dd..79b406d0f5c13 100644 --- a/site/tsconfig.json +++ b/site/tsconfig.json @@ -7,8 +7,8 @@ "jsx": "react-jsx", "jsxImportSource": "@emotion/react", "lib": ["dom", "dom.iterable", "esnext"], - "module": "esnext", - "moduleResolution": "node", + "module": "preserve", + "moduleResolution": "bundler", "noEmit": true, "outDir": "build/", "preserveWatchOutput": true, @@ -16,9 +16,9 @@ "skipLibCheck": true, "strict": true, "target": "es2020", + "types": ["jest", "node", "react", "react-dom", "vite/client"], "baseUrl": "src/" }, "include": ["**/*.ts", "**/*.tsx"], - "exclude": ["node_modules/", "_jest"], - "types": ["@emotion/react", "@testing-library/jest-dom", "jest", "node"] + "exclude": ["node_modules/"] } diff --git a/site/tsconfig.test.json b/site/tsconfig.test.json deleted file mode 100644 index c6f5e679af857..0000000000000 --- a/site/tsconfig.test.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "extends": "./tsconfig.json", - "exclude": ["node_modules", "_jest"], - "include": ["**/*.stories.tsx", "**/*.test.tsx", "**/*.d.ts"] -} From 6fbe7773171ab71e06c495a9dfa52e6b57f90880 Mon Sep 17 00:00:00 2001 From: DevCats Date: Fri, 22 Aug 2025 12:53:33 -0500 Subject: [PATCH 2/4] chore: add auggie icon (#19500) add auggie icon --- site/src/theme/externalImages.ts | 1 + site/src/theme/icons.json | 1 + site/static/icon/auggie.svg | 8 ++++++++ 3 files changed, 10 insertions(+) create mode 100644 site/static/icon/auggie.svg diff --git a/site/src/theme/externalImages.ts b/site/src/theme/externalImages.ts index 15713559036d0..96515725bcfbc 100644 --- a/site/src/theme/externalImages.ts +++ b/site/src/theme/externalImages.ts @@ -142,6 +142,7 @@ export function getExternalImageStylesFromUrl( */ export const defaultParametersForBuiltinIcons = new Map([ ["/icon/apple-black.svg", "monochrome"], + ["/icon/auggie.svg", "monochrome"], ["/icon/aws.png", "whiteWithColor&brightness=1.5"], ["/icon/aws.svg", "blackWithColor&brightness=1.5"], ["/icon/aws-monochrome.svg", "monochrome"], diff --git a/site/src/theme/icons.json b/site/src/theme/icons.json index a9ed1ef361370..7c87468411e92 100644 --- a/site/src/theme/icons.json +++ b/site/src/theme/icons.json @@ -7,6 +7,7 @@ "apple-black.svg", "apple-grey.svg", "argo-workflows.svg", + "auggie.svg", "aws-dark.svg", "aws-light.svg", "aws-monochrome.svg", diff --git a/site/static/icon/auggie.svg b/site/static/icon/auggie.svg new file mode 100644 index 0000000000000..590bd5aa1e62a --- /dev/null +++ b/site/static/icon/auggie.svg @@ -0,0 +1,8 @@ + + + + + + + + From cde5b624f48ebb65fdb6be0d0cd23aa851b6b88c Mon Sep 17 00:00:00 2001 From: Bruno Quaresma Date: Fri, 22 Aug 2025 15:24:32 -0300 Subject: [PATCH 3/4] feat: display the number of idle tasks in the navbar (#19471) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Depends on: https://github.com/coder/coder/pull/19377 Closes https://github.com/coder/coder/issues/19323 **Screenshot:** Screenshot 2025-08-21 at 11 52 21 **Screen recording:** https://github.com/user-attachments/assets/f70b34fe-952b-427b-9bc9-71961ca23201 ## Summary by CodeRabbit - New Features - Added a Tasks navigation item showing a badge with the number of idle tasks and a tooltip: “You have X tasks waiting for input.” - Improvements - Fetches per-user tasks with periodic refresh for up-to-date counts. - Updated active styling for the Tasks link for clearer navigation state. - User menu now always appears on medium+ screens. - Tests - Expanded Storybook with preloaded, user-filtered task scenarios to showcase idle/task states. --- .../dashboard/Navbar/NavbarView.stories.tsx | 55 ++++++++- .../modules/dashboard/Navbar/NavbarView.tsx | 112 ++++++++++++++---- 2 files changed, 139 insertions(+), 28 deletions(-) diff --git a/site/src/modules/dashboard/Navbar/NavbarView.stories.tsx b/site/src/modules/dashboard/Navbar/NavbarView.stories.tsx index 786f595d32932..6b44ab0911024 100644 --- a/site/src/modules/dashboard/Navbar/NavbarView.stories.tsx +++ b/site/src/modules/dashboard/Navbar/NavbarView.stories.tsx @@ -1,13 +1,31 @@ import { chromaticWithTablet } from "testHelpers/chromatic"; -import { MockUserMember, MockUserOwner } from "testHelpers/entities"; +import { + MockUserMember, + MockUserOwner, + MockWorkspace, + MockWorkspaceAppStatus, +} from "testHelpers/entities"; import { withDashboardProvider } from "testHelpers/storybook"; import type { Meta, StoryObj } from "@storybook/react-vite"; import { userEvent, within } from "storybook/test"; import { NavbarView } from "./NavbarView"; +const tasksFilter = { + username: MockUserOwner.username, +}; + const meta: Meta = { title: "modules/dashboard/NavbarView", - parameters: { chromatic: chromaticWithTablet, layout: "fullscreen" }, + parameters: { + chromatic: chromaticWithTablet, + layout: "fullscreen", + queries: [ + { + key: ["tasks", tasksFilter], + data: [], + }, + ], + }, component: NavbarView, args: { user: MockUserOwner, @@ -78,3 +96,36 @@ export const CustomLogo: Story = { logo_url: "/icon/github.svg", }, }; + +export const IdleTasks: Story = { + parameters: { + queries: [ + { + key: ["tasks", tasksFilter], + data: [ + { + prompt: "Task 1", + workspace: { + ...MockWorkspace, + latest_app_status: { + ...MockWorkspaceAppStatus, + state: "idle", + }, + }, + }, + { + prompt: "Task 2", + workspace: MockWorkspace, + }, + { + prompt: "Task 3", + workspace: { + ...MockWorkspace, + latest_app_status: MockWorkspaceAppStatus, + }, + }, + ], + }, + ], + }, +}; diff --git a/site/src/modules/dashboard/Navbar/NavbarView.tsx b/site/src/modules/dashboard/Navbar/NavbarView.tsx index 4a2b3027a47dd..0cafaa8fdd46f 100644 --- a/site/src/modules/dashboard/Navbar/NavbarView.tsx +++ b/site/src/modules/dashboard/Navbar/NavbarView.tsx @@ -1,13 +1,21 @@ import { API } from "api/api"; import type * as TypesGen from "api/typesGenerated"; +import { Badge } from "components/Badge/Badge"; import { Button } from "components/Button/Button"; import { ExternalImage } from "components/ExternalImage/ExternalImage"; import { CoderIcon } from "components/Icons/CoderIcon"; +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from "components/Tooltip/Tooltip"; import type { ProxyContextValue } from "contexts/ProxyContext"; import { useWebpushNotifications } from "contexts/useWebpushNotifications"; import { useEmbeddedMetadata } from "hooks/useEmbeddedMetadata"; import { NotificationsInbox } from "modules/notifications/NotificationsInbox/NotificationsInbox"; import type { FC } from "react"; +import { useQuery } from "react-query"; import { NavLink, useLocation } from "react-router"; import { cn } from "utils/cn"; import { DeploymentDropdown } from "./DeploymentDropdown"; @@ -17,7 +25,7 @@ import { UserDropdown } from "./UserDropdown/UserDropdown"; interface NavbarViewProps { logo_url?: string; - user?: TypesGen.User; + user: TypesGen.User; buildInfo?: TypesGen.BuildInfoResponse; supportLinks?: readonly TypesGen.LinkConfig[]; onSignOut: () => void; @@ -60,7 +68,7 @@ export const NavbarView: FC = ({ )} - +
{proxyContextValue && ( @@ -109,16 +117,14 @@ export const NavbarView: FC = ({ } /> - {user && ( -
- -
- )} +
+ +
= ({ interface NavItemsProps { className?: string; + user: TypesGen.User; } -const NavItems: FC = ({ className }) => { +const NavItems: FC = ({ className, user }) => { const location = useLocation(); - const { metadata } = useEmbeddedMetadata(); return ( ); }; + +type TasksNavItemProps = { + user: TypesGen.User; +}; + +const TasksNavItem: FC = ({ user }) => { + const { metadata } = useEmbeddedMetadata(); + const canSeeTasks = Boolean( + metadata["tasks-tab-visible"].value || + process.env.NODE_ENV === "development" || + process.env.STORYBOOK, + ); + const filter = { + username: user.username, + }; + const { data: idleCount } = useQuery({ + queryKey: ["tasks", filter], + queryFn: () => API.experimental.getTasks(filter), + refetchInterval: 1_000 * 60, + enabled: canSeeTasks, + refetchOnWindowFocus: true, + initialData: [], + select: (data) => + data.filter((task) => task.workspace.latest_app_status?.state === "idle") + .length, + }); + + if (!canSeeTasks) { + return null; + } + + return ( + { + return cn(linkStyles.default, { [linkStyles.active]: isActive }); + }} + > + Tasks + {idleCount > 0 && ( + + + + + {idleCount} + + + {idleTasksLabel(idleCount)} + + + )} + + ); +}; + +function idleTasksLabel(count: number) { + return `You have ${count} ${count === 1 ? "task" : "tasks"} waiting for input`; +} From 3b6c85a3f907da92921627f07a0f586064b138e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=82=B1=E3=82=A4=E3=83=A9?= Date: Fri, 22 Aug 2025 13:40:24 -0600 Subject: [PATCH 4/4] chore: add @Parkreiner as site/ CODEOWNER (#19502) --- CODEOWNERS | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CODEOWNERS b/CODEOWNERS index 451b34835eea0..fde24a9d874ed 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -18,7 +18,7 @@ coderd/rbac/ @Emyrk scripts/apitypings/ @Emyrk scripts/gensite/ @aslilac -site/ @aslilac +site/ @aslilac @Parkreiner site/src/hooks/ @Parkreiner # These rules intentionally do not specify any owners. More specific rules # override less specific rules, so these files are "ignored" by the site/ rule. @@ -27,6 +27,7 @@ site/e2e/provisionerGenerated.ts site/src/api/countriesGenerated.ts site/src/api/rbacresourcesGenerated.ts site/src/api/typesGenerated.ts +site/src/testHelpers/entities.ts site/CLAUDE.md # The blood and guts of the autostop algorithm, which is quite complex and