From 89bc27bf8a4796888880a4083b500840f8f4a0c2 Mon Sep 17 00:00:00 2001 From: Katia Bulatova Date: Fri, 3 Jul 2026 11:50:20 +0000 Subject: [PATCH 01/10] chore(webapp): migrate to tailwind v4 CSS-first @theme in app/tailwind.css: static palettes plus semantic color tokens as CSS variables, overridable per theme. Tailwind runs through @tailwindcss/postcss (Remix integration off). Plugins upgraded or replaced with v4 equivalents; tailwind-merge bumped to v3. Utility classes codemodded by the official upgrade tool. Fixed two invalid arbitrary variants the upgrade surfaced (table sticky cell :has(), number input attribute variant). --- .server-changes/tailwind-v4-migration.md | 6 + apps/webapp/app/components/AskAI.tsx | 6 +- .../app/components/BackgroundWrapper.tsx | 6 +- .../app/components/DefinitionTooltip.tsx | 2 +- apps/webapp/app/components/Feedback.tsx | 2 +- apps/webapp/app/components/GitMetadata.tsx | 6 +- apps/webapp/app/components/ListPagination.tsx | 4 +- .../app/components/ProductHuntBanner.tsx | 2 +- apps/webapp/app/components/WarmStarts.tsx | 6 +- .../app/components/code/AIQueryInput.tsx | 2 +- .../app/components/code/ChartConfigPanel.tsx | 2 +- .../app/components/code/TSQLResultsTable.tsx | 12 +- .../DashboardAgentComposer.tsx | 2 +- .../dashboard-agent/DashboardAgentHistory.tsx | 2 +- apps/webapp/app/components/logs/LogsTable.tsx | 2 +- .../app/components/metrics/BigNumber.tsx | 2 +- .../app/components/metrics/QueryWidget.tsx | 4 +- .../metrics/SaveToDashboardDialog.tsx | 4 +- .../app/components/metrics/TitleWidget.tsx | 4 +- .../navigation/EnvironmentSelector.tsx | 8 +- .../navigation/HelpAndFeedbackPopover.tsx | 6 +- .../navigation/NotificationPanel.tsx | 4 +- .../app/components/navigation/SideMenu.tsx | 16 +- .../components/navigation/SideMenuItem.tsx | 6 +- .../onboarding/TechnologyPicker.tsx | 8 +- .../app/components/primitives/Alert.tsx | 2 +- .../components/primitives/AnimatingArrow.tsx | 10 +- .../components/primitives/AppliedFilter.tsx | 2 +- .../app/components/primitives/Avatar.tsx | 4 +- .../app/components/primitives/Buttons.tsx | 12 +- .../app/components/primitives/Calendar.tsx | 10 +- .../app/components/primitives/Callout.tsx | 6 +- .../app/components/primitives/Checkbox.tsx | 8 +- .../app/components/primitives/ClientTabs.tsx | 2 +- .../components/primitives/ClipboardField.tsx | 12 +- .../app/components/primitives/DateField.tsx | 4 +- .../components/primitives/DateTimePicker.tsx | 2 +- .../app/components/primitives/Dialog.tsx | 6 +- .../app/components/primitives/Input.tsx | 4 +- .../primitives/InputNumberStepper.tsx | 12 +- .../app/components/primitives/InputOTP.tsx | 6 +- .../primitives/LoadingBarDivider.tsx | 2 +- .../app/components/primitives/Pagination.tsx | 4 +- .../app/components/primitives/Popover.tsx | 6 +- .../app/components/primitives/RadioButton.tsx | 16 +- .../app/components/primitives/Resizable.tsx | 4 +- .../primitives/SegmentedControl.tsx | 2 +- .../app/components/primitives/Select.tsx | 12 +- .../app/components/primitives/ShortcutKey.tsx | 4 +- .../components/primitives/SimpleSelect.tsx | 6 +- .../app/components/primitives/Slider.tsx | 4 +- .../app/components/primitives/Switch.tsx | 12 +- .../app/components/primitives/Table.tsx | 14 +- .../app/components/primitives/Tooltip.tsx | 2 +- .../primitives/TreeView/TreeView.tsx | 2 +- .../components/primitives/UsageSparkline.tsx | 2 +- .../primitives/charts/BigNumberCard.tsx | 6 +- .../components/primitives/charts/Chart.tsx | 6 +- .../primitives/charts/ChartCard.tsx | 2 +- .../primitives/charts/ChartLegendCompound.tsx | 2 +- .../primitives/charts/ChartRoot.tsx | 2 +- .../primitives/charts/ChartZoom.tsx | 4 +- .../app/components/query/QueryEditor.tsx | 4 +- .../webapp/app/components/run/RunTimeline.tsx | 22 +- .../app/components/runs/v3/AIFilterInput.tsx | 4 +- .../components/runs/v3/ReplayRunDialog.tsx | 2 +- .../runs/v3/RunStatusCellTooltip.tsx | 2 +- apps/webapp/app/components/runs/v3/RunTag.tsx | 10 +- .../app/components/runs/v3/SharedFilters.tsx | 2 +- .../app/components/runs/v3/TaskRunsTable.tsx | 20 +- .../components/runs/v3/TaskTriggerSource.tsx | 14 +- .../runs/v3/agent/AgentMessageView.tsx | 2 +- .../components/runs/v3/ai/AIChatMessages.tsx | 2 +- .../components/runs/v3/ai/AIModelSummary.tsx | 2 +- .../components/runs/v3/ai/AISpanDetails.tsx | 2 +- .../components/runs/v3/ai/SpanMetricRow.tsx | 2 +- .../components/sessions/v1/SessionsTable.tsx | 2 +- .../route.tsx | 2 +- .../route.tsx | 4 +- .../route.tsx | 2 +- .../route.tsx | 6 +- .../route.tsx | 2 +- .../route.tsx | 2 +- .../route.tsx | 2 +- .../route.tsx | 2 +- .../route.tsx | 2 +- .../route.tsx | 2 +- .../route.tsx | 6 +- .../route.tsx | 4 +- .../route.tsx | 6 +- .../route.tsx | 6 +- .../route.tsx | 12 +- .../AITabContent.tsx | 2 +- .../QueryHistoryPopover.tsx | 6 +- .../route.tsx | 6 +- .../route.tsx | 4 +- .../route.tsx | 10 +- .../route.tsx | 2 +- .../route.tsx | 2 +- .../AIPayloadTabContent.tsx | 24 +- .../route.tsx | 12 +- .../route.tsx | 2 +- .../route.tsx | 2 +- .../webapp/app/routes/_app.orgs.new/route.tsx | 2 +- .../app/routes/_app.timezones/route.tsx | 4 +- .../route.tsx | 2 +- .../app/routes/account.security/route.tsx | 2 +- .../app/routes/confirm-basic-details.tsx | 2 +- apps/webapp/app/routes/invites.tsx | 2 +- apps/webapp/app/routes/login._index/route.tsx | 2 +- .../webapp/app/routes/resources.incidents.tsx | 6 +- .../route.tsx | 6 +- ...cts.$projectParam.env.$envParam.vercel.tsx | 2 +- ...ctParam.schedules.new.natural-language.tsx | 2 +- ...ces.orgs.$organizationSlug.select-plan.tsx | 4 +- .../app/routes/storybook.agent-ui/route.tsx | 2 +- .../app/routes/storybook.popover/route.tsx | 2 +- .../app/routes/storybook.table/route.tsx | 2 +- .../app/routes/storybook.timeline/route.tsx | 8 +- .../app/routes/unsubscribe.$userId.$token.tsx | 2 +- apps/webapp/app/routes/vercel.onboarding.tsx | 6 +- .../resolveRealtimeStreamClient.server.ts | 4 +- .../shadowRealtimeClientInstance.server.ts | 2 +- apps/webapp/app/tailwind.css | 408 +++++-- apps/webapp/app/utils/cn.ts | 74 +- apps/webapp/app/utils/randomWords.ts | 6 +- apps/webapp/app/v3/featureFlags.ts | 4 +- .../computeTemplateCreation.server.ts | 12 +- apps/webapp/package.json | 24 +- apps/webapp/postcss.config.js | 6 +- apps/webapp/remix.config.js | 4 +- apps/webapp/tailwind.config.js | 385 ------- pnpm-lock.yaml | 1005 ++++++----------- 133 files changed, 1039 insertions(+), 1517 deletions(-) create mode 100644 .server-changes/tailwind-v4-migration.md delete mode 100644 apps/webapp/tailwind.config.js diff --git a/.server-changes/tailwind-v4-migration.md b/.server-changes/tailwind-v4-migration.md new file mode 100644 index 00000000000..c1675ff21b7 --- /dev/null +++ b/.server-changes/tailwind-v4-migration.md @@ -0,0 +1,6 @@ +--- +area: webapp +type: improvement +--- + +Migrated the webapp to Tailwind CSS v4 with a CSS-first `@theme`. Semantic color tokens are now CSS variables overridable per theme (multi-theme support). Tailwind runs through `@tailwindcss/postcss`; plugins replaced or upgraded to v4-compatible versions. diff --git a/apps/webapp/app/components/AskAI.tsx b/apps/webapp/app/components/AskAI.tsx index bf76654cced..0276695f79d 100644 --- a/apps/webapp/app/components/AskAI.tsx +++ b/apps/webapp/app/components/AskAI.tsx @@ -173,7 +173,7 @@ function AskAIDialog({ initialQuery, isOpen, onOpenChange, closeAskAI }: AskAIDi return ( - +
Ask AI @@ -468,7 +468,7 @@ function ChatInterface({ initialQuery }: { initialQuery?: string }) { error={error} addFeedback={addFeedback} /> -
+
- + {title} {typeof content === "string" ? ( {content} diff --git a/apps/webapp/app/components/Feedback.tsx b/apps/webapp/app/components/Feedback.tsx index 683f39d34ab..ebd61180de5 100644 --- a/apps/webapp/app/components/Feedback.tsx +++ b/apps/webapp/app/components/Feedback.tsx @@ -84,7 +84,7 @@ export function Feedback({ button, defaultValue = "bug", onOpenChange }: Feedbac Contact us
- + How can we help? We read every message and will respond as quickly as we can. diff --git a/apps/webapp/app/components/GitMetadata.tsx b/apps/webapp/app/components/GitMetadata.tsx index fb53ee6bfea..3057de10425 100644 --- a/apps/webapp/app/components/GitMetadata.tsx +++ b/apps/webapp/app/components/GitMetadata.tsx @@ -28,7 +28,7 @@ export function GitMetadataBranch({ leadingIconClassName="group-hover/table-row:text-text-bright" iconSpacing="gap-x-1" to={git.branchUrl} - className="pl-1 duration-0 [&_span]:duration-0 [&_span]:group-hover/table-row:text-text-bright" + className="pl-1 duration-0 [&_span]:duration-0 group-hover/table-row:[&_span]:text-text-bright" > {git.branchName} @@ -52,7 +52,7 @@ export function GitMetadataCommit({ LeadingIcon={} leadingIconClassName="group-hover/table-row:text-text-bright" iconSpacing="gap-x-1" - className="pl-1 duration-0 [&_span]:duration-0 [&_span]:group-hover/table-row:text-text-bright" + className="pl-1 duration-0 [&_span]:duration-0 group-hover/table-row:[&_span]:text-text-bright" > {`${git.shortSha} / ${git.commitMessage}`} @@ -78,7 +78,7 @@ export function GitMetadataPullRequest({ LeadingIcon={} leadingIconClassName="group-hover/table-row:text-text-bright" iconSpacing="gap-x-1" - className="pl-1 duration-0 [&_span]:duration-0 [&_span]:group-hover/table-row:text-text-bright" + className="pl-1 duration-0 [&_span]:duration-0 group-hover/table-row:[&_span]:text-text-bright" > #{git.pullRequestNumber} {git.pullRequestTitle} diff --git a/apps/webapp/app/components/ListPagination.tsx b/apps/webapp/app/components/ListPagination.tsx index 6e26330677f..5d2459e39b8 100644 --- a/apps/webapp/app/components/ListPagination.tsx +++ b/apps/webapp/app/components/ListPagination.tsx @@ -41,7 +41,7 @@ function PreviousButton({ cursor }: { cursor?: string }) { variant={"secondary/small"} LeadingIcon={ChevronLeftIcon} className={cn( - "flex items-center rounded-r-none border-r-0 pl-2 pr-[0.5625rem]", + "flex items-center rounded-r-none border-r-0 pl-2 pr-2.25", !path && "cursor-not-allowed opacity-50" )} onClick={(e) => !path && e.preventDefault()} @@ -63,7 +63,7 @@ function NextButton({ cursor }: { cursor?: string }) { variant={"secondary/small"} TrailingIcon={ChevronRightIcon} className={cn( - "flex items-center rounded-l-none border-l-0 pl-[0.5625rem] pr-2", + "flex items-center rounded-l-none border-l-0 pl-2.25 pr-2", !path && "cursor-not-allowed opacity-50" )} onClick={(e) => !path && e.preventDefault()} diff --git a/apps/webapp/app/components/ProductHuntBanner.tsx b/apps/webapp/app/components/ProductHuntBanner.tsx index b85893836a1..3508104e2d9 100644 --- a/apps/webapp/app/components/ProductHuntBanner.tsx +++ b/apps/webapp/app/components/ProductHuntBanner.tsx @@ -14,7 +14,7 @@ export function ProductHuntBanner() { Vote for us today only! diff --git a/apps/webapp/app/components/WarmStarts.tsx b/apps/webapp/app/components/WarmStarts.tsx index 07a894b9e7b..50ffeee7997 100644 --- a/apps/webapp/app/components/WarmStarts.tsx +++ b/apps/webapp/app/components/WarmStarts.tsx @@ -30,7 +30,7 @@ export function WarmStartIconWithTooltip({ }) { return ( } content={} /> @@ -42,14 +42,14 @@ function WarmStartTooltipContent() {
- + A cold start happens when we need to boot up a new machine for your run to execute. This takes longer than a warm start.
- + A warm start happens when we can reuse a machine from a run that recently finished. This takes less time than a cold start. diff --git a/apps/webapp/app/components/code/AIQueryInput.tsx b/apps/webapp/app/components/code/AIQueryInput.tsx index 8e804958db2..39dfaf338ac 100644 --- a/apps/webapp/app/components/code/AIQueryInput.tsx +++ b/apps/webapp/app/components/code/AIQueryInput.tsx @@ -257,7 +257,7 @@ export function AIQueryInput({ onChange={(e) => setPrompt(e.target.value)} disabled={isLoading} rows={8} - className="m-0 min-h-10 w-full resize-none border-0 bg-background-bright px-3 py-2.5 text-sm text-text-bright scrollbar-thin scrollbar-track-transparent scrollbar-thumb-charcoal-600 file:border-0 file:bg-transparent file:text-base file:font-medium placeholder:text-text-dimmed focus:border-0 focus:outline-none focus:ring-0 focus-visible:outline-none focus-visible:ring-0 focus-visible:ring-offset-0 disabled:cursor-not-allowed disabled:opacity-50" + className="m-0 min-h-10 w-full resize-none border-0 bg-background-bright px-3 py-2.5 text-sm text-text-bright scrollbar-thin scrollbar-track-transparent scrollbar-thumb-charcoal-600 file:border-0 file:bg-transparent file:text-base file:font-medium placeholder:text-text-dimmed focus:border-0 focus:outline-hidden focus:ring-0 focus-visible:outline-hidden focus-visible:ring-0 focus-visible:ring-offset-0 disabled:cursor-not-allowed disabled:opacity-50" onKeyDown={(e) => { if (e.key === "Enter" && !e.shiftKey && prompt.trim() && !isLoading) { e.preventDefault(); diff --git a/apps/webapp/app/components/code/ChartConfigPanel.tsx b/apps/webapp/app/components/code/ChartConfigPanel.tsx index 1b61e46a752..73a2c94ac92 100644 --- a/apps/webapp/app/components/code/ChartConfigPanel.tsx +++ b/apps/webapp/app/components/code/ChartConfigPanel.tsx @@ -568,7 +568,7 @@ function SeriesColorPicker({
@@ -1249,7 +1249,7 @@ export const TSQLResultsTable = memo(function TSQLResultsTable({ height: `${rowVirtualizer.getTotalSize()}px`, position: "relative", }} - className="divide-y divide-charcoal-700 bg-background-bright after:absolute after:bottom-0 after:left-0 after:right-0 after:z-[1] after:h-px after:bg-grid-bright" + className="divide-y divide-charcoal-700 bg-background-bright after:absolute after:bottom-0 after:left-0 after:right-0 after:z-1 after:h-px after:bg-grid-bright" > {rowVirtualizer.getVirtualItems().map((virtualRow) => { const row = tableRows[virtualRow.index]; diff --git a/apps/webapp/app/components/dashboard-agent/DashboardAgentComposer.tsx b/apps/webapp/app/components/dashboard-agent/DashboardAgentComposer.tsx index 6f681346175..8d800aa2870 100644 --- a/apps/webapp/app/components/dashboard-agent/DashboardAgentComposer.tsx +++ b/apps/webapp/app/components/dashboard-agent/DashboardAgentComposer.tsx @@ -34,7 +34,7 @@ export function DashboardAgentComposer({ }} placeholder="Type a message…" className={cn( - "max-h-[40vh] min-h-[40px] flex-1 resize-none border-0 bg-transparent px-2 py-1.5 text-sm text-text-bright placeholder-text-dimmed outline-none ring-0 scrollbar-thin scrollbar-track-transparent scrollbar-thumb-charcoal-600 [field-sizing:content] focus:outline-none focus:ring-0" + "max-h-[40vh] min-h-[40px] flex-1 resize-none border-0 bg-transparent px-2 py-1.5 text-sm text-text-bright placeholder-text-dimmed outline-hidden ring-0 scrollbar-thin scrollbar-track-transparent scrollbar-thumb-charcoal-600 field-sizing-content focus:outline-hidden focus:ring-0" )} /> {isStreaming ? ( diff --git a/apps/webapp/app/components/dashboard-agent/DashboardAgentHistory.tsx b/apps/webapp/app/components/dashboard-agent/DashboardAgentHistory.tsx index af19d0d69c7..e6eee3d4f66 100644 --- a/apps/webapp/app/components/dashboard-agent/DashboardAgentHistory.tsx +++ b/apps/webapp/app/components/dashboard-agent/DashboardAgentHistory.tsx @@ -53,7 +53,7 @@ export function DashboardAgentHistory({ @@ -143,7 +143,7 @@ export function NotificationPanel({ card )}
- + {card} diff --git a/apps/webapp/app/components/navigation/SideMenu.tsx b/apps/webapp/app/components/navigation/SideMenu.tsx index 395932f39e2..b0882da8a25 100644 --- a/apps/webapp/app/components/navigation/SideMenu.tsx +++ b/apps/webapp/app/components/navigation/SideMenu.tsx @@ -283,7 +283,7 @@ export function SideMenu({
@@ -762,7 +762,7 @@ function V3DeprecationPanel({ > + } @@ -774,7 +774,7 @@ function V3DeprecationPanel({ />
- + @@ -801,7 +801,7 @@ function V3DeprecationContent() { fullWidth TrailingIcon={ArrowTopRightOnSquareIcon} trailingIconClassName="text-amber-300" - className="border-amber-500/30 bg-amber-500/15 hover:!border-amber-500/50 hover:!bg-amber-500/25" + className="border-amber-500/30 bg-amber-500/15 hover:border-amber-500/50! hover:bg-amber-500/25!" > View migration guide
@@ -844,7 +844,7 @@ function ProjectSelector({ button={ @@ -876,12 +876,12 @@ function ProjectSelector({ side="right" sideOffset={8} hidden={!isCollapsed} - buttonClassName="!h-8" + buttonClassName="h-8!" asChild disableHoverableContent />