Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions products/desktop/docs/CONVENTIONS.md
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,12 @@ When creating reusable styled components, accept both `className?: string` and `

Default line heights are set in [packages/ui/src/styles/globals.css](../packages/ui/src/styles/globals.css). Add `leading-*` only when the component needs a non-default line height. Pair arbitrary body text sizes with `leading-snug`; pair titles with `leading-tight`.

Spinning icons go through `Spin` or `Spinner` from [packages/ui/src/primitives/Spinner.tsx](../packages/ui/src/primitives/Spinner.tsx). Never put `animate-spin` on an `<svg>`: Chromium animates SVG transforms on the main thread, so one visible spinner costs a style recalc and a layerize pass on every frame. Stop the animation (`spinning={false}`) or unmount the spinner when it is hidden behind `opacity-0`.

Do not write a `:has()` rule with `html` or `body` as the anchor and a descendant subject (`body:has(...) .thing`). Chromium re-checks such a rule after DOM mutations anywhere and restyles the whole document. Set custom properties on the anchor instead and consume them where the style applies, as the quill portal rule in `globals.css` does.

Animate `transform` and `opacity` only. Keyframes on `left`, `width`, or `height` force a layout every frame.

## Logging

Do not use `console.*` in source. Inject `ROOT_LOGGER` as `RootLogger`, then scope it.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,12 @@ describe("PreBaselineState", () => {
});

expect(screen.getByText("Establishing the baseline")).toBeVisible();
expect(
screen.getByRole("status", { name: "Loading" }).parentElement,
).toHaveClass("animate-spin", "motion-reduce:animate-none");
Comment thread
charlesvien marked this conversation as resolved.
expect(screen.getByRole("status", { name: "Loading" })).toHaveClass(
"motion-safe:animate-spin",
"motion-reduce:animate-none",
"size-5",
"animate-none",
);
const metrics = screen.getByRole("status", {
name: "Loading autoresearch metrics",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { MagnifyingGlass } from "@phosphor-icons/react";
import type { AutoresearchRun } from "@posthog/core/autoresearch/schemas";
import { Spinner } from "@posthog/quill";
import { Spin } from "@posthog/ui/primitives/Spinner";
import { Badge, Skeleton, Text } from "@radix-ui/themes";

export interface SessionActivity {
Expand All @@ -24,7 +25,9 @@ export function PreBaselineState({
<div className="flex items-start gap-3 rounded-md border border-blue-6 bg-blue-2 px-3 py-3">
{live && (
<span className="relative mt-0.5 size-5 shrink-0">
<Spinner className="size-5 motion-safe:animate-spin motion-reduce:animate-none" />
<Spin className="size-5 motion-reduce:animate-none">
<Spinner className="size-5 animate-none" />
</Spin>
Comment thread
posthog[bot] marked this conversation as resolved.
</span>
)}
<div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
TooltipTrigger,
} from "@posthog/quill";
import type { CanvasDiagnostic } from "@posthog/shared";
import { Spin } from "@posthog/ui/primitives/Spinner";
import { toast } from "@posthog/ui/primitives/toast";
import { useMutation, useQueryClient } from "@tanstack/react-query";
import { useEffect, useState } from "react";
Expand Down Expand Up @@ -106,7 +107,9 @@ export function CanvasBuildStatus({
className="flex items-center gap-1"
data-testid="canvas-build-active"
>
<SpinnerGapIcon size={14} className="animate-spin text-gray-9" />
<Spin className="text-gray-9">
<SpinnerGapIcon size={14} />
</Spin>
<Text size="xs" variant="muted">
{active.buildStatus === "queued" ? "Queued" : "Building"} · {elapsed}
</Text>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { useCanvasChatPanelStore } from "@posthog/ui/features/canvas/stores/canv
import type { EditorHandle } from "@posthog/ui/features/message-editor/types";
import { EmbeddedSessionView } from "@posthog/ui/features/sessions/components/EmbeddedSessionView";
import { taskDetailQuery } from "@posthog/ui/features/tasks/queries";
import { Spin } from "@posthog/ui/primitives/Spinner";
import { useQuery } from "@tanstack/react-query";
import { type Ref, useEffect, useRef } from "react";

Expand Down Expand Up @@ -165,7 +166,9 @@ function CanvasChatLoader({ taskId }: { taskId: string }) {
if (!task) {
return (
<div className="flex h-full items-center justify-center">
<SpinnerGapIcon size={18} className="animate-spin text-gray-9" />
<Spin className="text-gray-9">
<SpinnerGapIcon size={18} />
</Spin>
</div>
);
}
Expand Down Expand Up @@ -193,7 +196,9 @@ function CanvasCommentsLoader({
if (!task) {
return (
<div className="flex h-full items-center justify-center">
<SpinnerGapIcon size={18} className="animate-spin text-gray-9" />
<Spin className="text-gray-9">
<SpinnerGapIcon size={18} />
</Spin>
</div>
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ import { useCommentsQuery } from "@posthog/ui/features/sessions/components/useCo
import { useSessionForTask } from "@posthog/ui/features/sessions/useSession";
import { taskDetailQuery } from "@posthog/ui/features/tasks/queries";
import { ResizableSidebar } from "@posthog/ui/primitives/ResizableSidebar";
import { Spin } from "@posthog/ui/primitives/Spinner";
import { toast } from "@posthog/ui/primitives/toast";
import { track } from "@posthog/ui/shell/analytics";
import {
Expand Down Expand Up @@ -924,10 +925,9 @@ export function FreeformCanvasView({
{interactive &&
(isGenerating && effectiveTaskId ? (
<>
<SpinnerGapIcon
size={14}
className="animate-spin text-accent-9"
/>
<Spin className="text-accent-9">
<SpinnerGapIcon size={14} />
</Spin>
<Text size="1" className="text-gray-10">
Generating
</Text>
Expand Down Expand Up @@ -1293,7 +1293,9 @@ function LoadingState() {
<Empty className="h-full">
<EmptyHeader>
<EmptyMedia variant="icon">
<SpinnerGapIcon size={18} className="animate-spin text-accent-9" />
<Spin className="text-accent-9">
<SpinnerGapIcon size={18} />
</Spin>
</EmptyMedia>
<EmptyTitle>Loading canvas</EmptyTitle>
</EmptyHeader>
Expand All @@ -1314,7 +1316,9 @@ function GeneratingState({
<Empty className="h-full border-0">
<EmptyHeader>
<EmptyMedia variant="icon">
<SpinnerGapIcon size={18} className="animate-spin text-accent-9" />
<Spin className="text-accent-9">
<SpinnerGapIcon size={18} />
</Spin>
</EmptyMedia>
<EmptyTitle>Generating</EmptyTitle>
<EmptyDescription>An agent is building this canvas.</EmptyDescription>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { ArrowsClockwise, WifiHigh, WifiSlash } from "@phosphor-icons/react";
import { useService } from "@posthog/di/react";
import { useConnectivity } from "@posthog/ui/hooks/useConnectivity";
import { Spin } from "@posthog/ui/primitives/Spinner";
import { Box } from "@radix-ui/themes";
import { AnimatePresence, motion } from "framer-motion";
import { useEffect, useRef, useState } from "react";
Expand Down Expand Up @@ -101,10 +102,9 @@ function OfflineRow({
onClick={onRetry}
className="flex shrink-0 items-center gap-1.5 rounded-2 bg-(--amber-a4) px-2 py-1 font-medium text-(--amber-11) text-[12px] transition-colors hover:bg-(--amber-a5) disabled:opacity-60"
>
<ArrowsClockwise
size={13}
className={isChecking ? "animate-spin" : undefined}
/>
<Spin spinning={isChecking}>
<ArrowsClockwise size={13} />
</Spin>
{isChecking ? "Checking…" : "Retry"}
</button>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import {
} from "@posthog/quill";
import type { RegisteredFolder } from "@posthog/ui/features/folders/types";
import { useFolders } from "@posthog/ui/features/folders/useFolders";
import { Spin } from "@posthog/ui/primitives/Spinner";
import { toast } from "@posthog/ui/primitives/toast";
import { FIELD_TRIGGER_CLASS } from "@posthog/ui/styles/fieldTrigger";
import { Text } from "@radix-ui/themes";
Expand Down Expand Up @@ -178,10 +179,9 @@ export function FolderPicker({
</Text>
</div>
{isOpening ? (
<CircleNotch
size={14}
className="shrink-0 animate-spin text-(--gray-9)"
/>
<Spin className="shrink-0 text-(--gray-9)">
<CircleNotch size={14} />
</Spin>
) : (
<CaretDown size={14} className="shrink-0 text-(--gray-9)" />
)}
Expand All @@ -195,7 +195,9 @@ export function FolderPicker({
{isOpening ? "Opening..." : displayValue || placeholder}
</span>
{isOpening ? (
<CircleNotch size={10} className="animate-spin text-muted-foreground" />
<Spin className="text-muted-foreground">
<CircleNotch size={10} />
</Spin>
) : (
<CaretDown size={10} weight="bold" className="text-muted-foreground" />
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
Spinner,
Text,
} from "@posthog/quill";
import { Spin } from "@posthog/ui/primitives/Spinner";
import { Tooltip } from "@posthog/ui/primitives/Tooltip";
import { FIELD_TRIGGER_CLASS } from "@posthog/ui/styles/fieldTrigger";
import { defaultFilter } from "cmdk";
Expand Down Expand Up @@ -290,10 +291,9 @@ export function GitHubRepoPicker({
onRefresh();
}}
>
<ArrowClockwise
size={14}
className={isRefreshing ? "animate-spin" : undefined}
/>
<Spin spinning={isRefreshing}>
<ArrowClockwise size={14} />
</Spin>
</InputGroupButton>
</InputGroupAddon>
) : null}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import {
Check,
GitBranch,
Plus,
Spinner,
} from "@phosphor-icons/react";
import { useService } from "@posthog/di/react";
import { useHostTRPC } from "@posthog/host-router/react";
Expand All @@ -26,6 +25,7 @@ import type {
GitBusyOperation,
GitBusyState,
} from "@posthog/shared/domain-types";
import { Spin, Spinner } from "@posthog/ui/primitives/Spinner";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { type RefObject, useEffect, useMemo, useRef, useState } from "react";
import { Tooltip } from "../../../primitives/Tooltip";
Expand Down Expand Up @@ -59,7 +59,7 @@ const USE_INPUT_BRANCH_ACTION = "__use_input_branch__";
function LoadingRow({ label }: { label: string }) {
return (
<div className="flex items-center gap-1 px-2 py-1.5 text-muted-foreground text-xs">
<Spinner size={12} className="animate-spin" />
<Spinner size={12} />
{label}
</div>
);
Expand Down Expand Up @@ -386,7 +386,7 @@ export function BranchSelector({
className="min-w-0 max-w-[250px] shrink"
>
{showSpinner ? (
<Spinner size={14} className="shrink-0 animate-spin" />
<Spinner size={14} className="shrink-0" />
) : (
<GitBranch size={14} weight="regular" className="shrink-0" />
)}
Expand Down Expand Up @@ -470,10 +470,9 @@ export function BranchSelector({
onRefresh();
}}
>
<ArrowClockwise
size={14}
className={isRefreshing ? "animate-spin" : undefined}
/>
<Spin spinning={isRefreshing}>
<ArrowClockwise size={14} />
</Spin>
</InputGroupButton>
) : null}
</InputGroupAddon>
Expand Down Expand Up @@ -586,7 +585,7 @@ export function BranchSelector({
>
{cloudBranchesFetchingMore ? (
<>
<Spinner size={14} className="animate-spin" />
<Spinner size={14} />
Loading more…
</>
) : (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import type { SignalSourceValues } from "@posthog/ui/features/inbox/components/S
import { InboxBadge } from "@posthog/ui/features/inbox/components/utils/InboxBadge";
import { getSourceProductMeta } from "@posthog/ui/features/inbox/components/utils/source-product-icons";
import { Badge } from "@posthog/ui/primitives/Badge";
import { Spin } from "@posthog/ui/primitives/Spinner";
import { Box, Flex, Spinner, Switch, Text } from "@radix-ui/themes";
import { type ComponentType, memo, useCallback } from "react";

Expand Down Expand Up @@ -225,10 +226,9 @@ const ResponderAgentCard = memo(function ResponderAgentCard({

{armed && agent.source === "session_replay" && status === "syncing" ? (
<Flex align="center" gap="2" className="mt-2 ml-8">
<CircleNotchIcon
size={14}
className="animate-spin text-(--accent-11)"
/>
<Spin className="text-(--accent-11)">
<CircleNotchIcon size={14} />
</Spin>
<Text className="text-(--accent-11) text-[13px]">
Session analysis run in progress…
</Text>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
import { GitHubSourceRepositoriesDialog } from "@posthog/ui/features/inbox/components/GitHubSourceRepositoriesDialog";
import { getSourceProductMeta } from "@posthog/ui/features/inbox/components/utils/source-product-icons";
import { Badge } from "@posthog/ui/primitives/Badge";
import { Spin } from "@posthog/ui/primitives/Spinner";
import { memo, useCallback, useState } from "react";

export type SignalSourceValues = Record<ToggleableSourceProduct, boolean>;
Expand Down Expand Up @@ -317,7 +318,9 @@ function SourceRunningIndicator({
}
return (
<div className="mt-2 flex items-center gap-2">
<CircleNotchIcon size={14} className="animate-spin text-(--accent-11)" />
<Spin className="text-(--accent-11)">
<CircleNotchIcon size={14} />
</Spin>
<span className="text-(--accent-11) text-[13px]">{message}</span>
</div>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { ANALYTICS_EVENTS } from "@posthog/shared/analytics-events";
import { StopCloudRunDialog } from "@posthog/ui/features/sessions/components/StopCloudRunDialog";
import { Badge } from "@posthog/ui/primitives/Badge";
import { Button } from "@posthog/ui/primitives/Button";
import { Spin } from "@posthog/ui/primitives/Spinner";
import { toast } from "@posthog/ui/primitives/toast";
import { navigateToTaskDetail } from "@posthog/ui/router/navigationBridge";
import { track } from "@posthog/ui/shell/analytics";
Expand Down Expand Up @@ -145,11 +146,9 @@ export function LoopRunRow({
<Flex direction="column" className="min-w-0 gap-1.5">
<Flex align="center" gap="2" wrap="wrap">
<Badge color={statusColor(run.status)}>
<StatusIcon
size={10}
weight="bold"
className={cn(run.status === "in_progress" && "animate-spin")}
/>
<Spin spinning={run.status === "in_progress"}>
<StatusIcon size={10} weight="bold" />
</Spin>
{run.status.replaceAll("_", " ")}
</Badge>
<Text
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { CheckCircle, CircleNotch } from "@phosphor-icons/react";
import { PANEL_SHADOW } from "@posthog/ui/features/onboarding/components/onboardingStyles";
import { Spin } from "@posthog/ui/primitives/Spinner";
import { Box, Flex, Text } from "@radix-ui/themes";
import type { ReactNode } from "react";

Expand Down Expand Up @@ -33,7 +34,9 @@ export function CliCheckPanel({
</Text>
</Flex>
{isLoading ? (
<CircleNotch size={14} className="animate-spin text-(--gray-9)" />
<Spin className="text-(--gray-9)">
<CircleNotch size={14} />
</Spin>
) : (
statusBadge
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
} from "@phosphor-icons/react";
import { repoMatchesGitHubRepos } from "@posthog/core/onboarding/repoProvider";
import { cn } from "@posthog/quill";
import { Spin } from "@posthog/ui/primitives/Spinner";
import { useHostCapabilities } from "@posthog/ui/shell/useHostCapabilities";
import { Button, Flex, Text } from "@radix-ui/themes";
import { AnimatePresence, motion } from "framer-motion";
Expand Down Expand Up @@ -149,10 +150,9 @@ export function SelectRepoStep({
transition={{ duration: 0.15 }}
>
<Flex align="center" gap="2">
<CircleNotch
size={14}
className="animate-spin text-(--gray-9)"
/>
<Spin className="text-(--gray-9)">
<CircleNotch size={14} />
</Spin>
<Text className="text-(--gray-9) text-[13px]">
Detecting repository...
</Text>
Expand Down
Loading
Loading