From 28770c7f1e4e4c9b05ded130090a3689b3238861 Mon Sep 17 00:00:00 2001 From: klopez4212 Date: Fri, 12 Jun 2026 15:05:32 +0100 Subject: [PATCH 1/3] Add shared skeleton loader primitives --- desktop/src/shared/styles/globals.css | 125 ++++++++++++++++++++++++++ desktop/src/shared/ui/skeleton.tsx | 94 +++++++++++++++++-- desktop/src/shared/ui/spinner.tsx | 32 +++++-- 3 files changed, 237 insertions(+), 14 deletions(-) diff --git a/desktop/src/shared/styles/globals.css b/desktop/src/shared/styles/globals.css index bb3fdb2492..3cb2da5230 100644 --- a/desktop/src/shared/styles/globals.css +++ b/desktop/src/shared/styles/globals.css @@ -2,6 +2,16 @@ @import "tw-animate-css"; @config "../../../tailwind.config.js"; +.sprout-arc-spinner { + animation: sprout-arc-spinner-spin 500ms linear infinite; +} + +@keyframes sprout-arc-spinner-spin { + to { + transform: rotate(360deg); + } +} + .buzz-emoji-mart { align-items: stretch; display: flex; @@ -837,6 +847,121 @@ } } +:root { + --skel-pulse-dur: 1000ms; + --skel-pulse-count: infinite; + --skel-pulse-min: 0.5; + --skel-reveal-dur: 400ms; + --skel-reveal-blur: 2px; + --skel-reveal-ease: ease-in-out; +} + +.t-skel { + position: relative; +} + +.t-skel[data-layout="flow"] { + display: grid; +} + +.t-skel-skeleton, +.t-skel-content { + inset: 0; + position: absolute; +} + +.t-skel[data-layout="flow"] > .t-skel-skeleton, +.t-skel[data-layout="flow"] > .t-skel-content { + grid-area: 1 / 1; + width: 100%; +} + +.t-skel[data-layout="flow"] > .t-skel-skeleton { + inset: auto; + position: relative; +} + +.t-skel[data-layout="flow"] > .t-skel-content, +.t-skel[data-layout="flow"].is-revealed > .t-skel-skeleton { + inset: 0; + position: absolute; +} + +.t-skel[data-layout="flow"].is-revealed > .t-skel-content { + inset: auto; + position: relative; +} + +.t-skel-skeleton { + filter: blur(0); + opacity: 1; + transition: + opacity var(--skel-reveal-dur) var(--skel-reveal-ease), + filter var(--skel-reveal-dur) var(--skel-reveal-ease); + z-index: 1; +} + +.t-skel-content { + filter: blur(var(--skel-reveal-blur)); + opacity: 0; + pointer-events: none; + transition: + opacity var(--skel-reveal-dur) var(--skel-reveal-ease), + filter var(--skel-reveal-dur) var(--skel-reveal-ease); + z-index: 2; +} + +.t-skel.is-revealed .t-skel-skeleton { + filter: blur(var(--skel-reveal-blur)); + opacity: 0; + pointer-events: none; +} + +.t-skel.is-revealed .t-skel-content { + filter: blur(0); + opacity: 1; + pointer-events: auto; +} + +.t-skel.is-resetting .t-skel-skeleton, +.t-skel.is-resetting .t-skel-content { + transition: none; +} + +.t-skel-bar.is-pulsing, +.t-skel-skeleton.is-pulsing > :not(:has(.t-skel-bar)) { + animation: t-skel-pulse var(--skel-pulse-dur) ease-in-out + var(--skel-pulse-count); +} + +.t-skel.is-revealed .t-skel-bar.is-pulsing, +.t-skel.is-revealed .t-skel-skeleton.is-pulsing > :not(:has(.t-skel-bar)) { + animation: none; +} + +@keyframes t-skel-pulse { + 0%, + 100% { + opacity: 1; + } + + 50% { + opacity: var(--skel-pulse-min); + } +} + +@media (prefers-reduced-motion: reduce) { + .t-skel-skeleton, + .t-skel-content { + transition: none; + } + + .t-skel-bar.is-pulsing, + .t-skel-skeleton.is-pulsing > :not(:has(.t-skel-bar)) { + animation: none; + } +} + @layer components { .buzz-startup-shell { min-height: 100dvh; diff --git a/desktop/src/shared/ui/skeleton.tsx b/desktop/src/shared/ui/skeleton.tsx index 563cc4c9ef..b324eb5da4 100644 --- a/desktop/src/shared/ui/skeleton.tsx +++ b/desktop/src/shared/ui/skeleton.tsx @@ -1,15 +1,99 @@ +import * as React from "react"; + import { cn } from "@/shared/lib/cn"; -function Skeleton({ +type SkeletonProps = React.HTMLAttributes & { + pulsing?: boolean; +}; + +function Skeleton({ className, pulsing = true, ...props }: SkeletonProps) { + return ( + @@ -109,7 +109,7 @@ export function WorkflowWebhookHeadersEditor({ type="button" variant="ghost" > - + ))} diff --git a/desktop/src/features/workflows/ui/workflowFormPrimitives.tsx b/desktop/src/features/workflows/ui/workflowFormPrimitives.tsx index e6173ee23d..ca0f8a8724 100644 --- a/desktop/src/features/workflows/ui/workflowFormPrimitives.tsx +++ b/desktop/src/features/workflows/ui/workflowFormPrimitives.tsx @@ -25,7 +25,7 @@ export function FormSelect({ > {children} - + ); } diff --git a/desktop/src/features/workspaces/ui/WorkspaceSwitcher.tsx b/desktop/src/features/workspaces/ui/WorkspaceSwitcher.tsx index 2e7e14d045..89b17a93ca 100644 --- a/desktop/src/features/workspaces/ui/WorkspaceSwitcher.tsx +++ b/desktop/src/features/workspaces/ui/WorkspaceSwitcher.tsx @@ -121,7 +121,7 @@ export function WorkspaceSwitcher({ data-testid="relay-connection-warning" role="img" > - + @@ -154,8 +154,8 @@ export function WorkspaceSwitcher({ )} @@ -209,7 +209,7 @@ export function WorkspaceSwitcher({ > {activeWorkspace?.id === workspace.id ? ( - + ) : null} @@ -226,7 +226,7 @@ export function WorkspaceSwitcher({ }} type="button" > - + ))} @@ -296,7 +296,7 @@ export function WorkspaceSwitcher({ > {activeWorkspace?.id === workspace.id ? ( - + ) : null} {workspace.name} @@ -311,7 +311,7 @@ export function WorkspaceSwitcher({ }} type="button" > - + ))} diff --git a/desktop/src/shared/ui/VideoPlayer.tsx b/desktop/src/shared/ui/VideoPlayer.tsx index b967b00e95..5dd42bff98 100644 --- a/desktop/src/shared/ui/VideoPlayer.tsx +++ b/desktop/src/shared/ui/VideoPlayer.tsx @@ -1627,7 +1627,7 @@ function VideoReviewDialog({ type="button" onClick={() => setIsEmojiPickerOpen((open) => !open)} > - + More reactions @@ -1900,7 +1900,7 @@ function VideoReviewCommentBody({

{reactions.some((reaction) => reaction.reactedByCurrentUser) ? ( ) : null} diff --git a/desktop/src/shared/ui/import-status-icon.tsx b/desktop/src/shared/ui/import-status-icon.tsx index 9617e44eba..aadb5b9bc6 100644 --- a/desktop/src/shared/ui/import-status-icon.tsx +++ b/desktop/src/shared/ui/import-status-icon.tsx @@ -16,7 +16,9 @@ export function ImportStatusIcon({ }) { switch (status) { case "importing": - return ; + return ( + + ); case "done": return ; case "error": diff --git a/desktop/src/shared/ui/markdown.tsx b/desktop/src/shared/ui/markdown.tsx index b21b45e732..2f4a84663b 100644 --- a/desktop/src/shared/ui/markdown.tsx +++ b/desktop/src/shared/ui/markdown.tsx @@ -520,7 +520,7 @@ function MarkdownCodeBlock({ type="button" variant="ghost" > - + Copy code block @@ -579,7 +579,7 @@ function FileCard({ className="my-1 inline-flex max-w-sm items-center gap-3 rounded-xl border border-border/70 bg-muted/40 px-3 py-2 text-left no-underline transition-colors hover:bg-muted/70" > - + diff --git a/web/src/features/repos/ui/RepoBlobViewer.tsx b/web/src/features/repos/ui/RepoBlobViewer.tsx index 0a53696d3b..5adfdd2f0e 100644 --- a/web/src/features/repos/ui/RepoBlobViewer.tsx +++ b/web/src/features/repos/ui/RepoBlobViewer.tsx @@ -258,7 +258,7 @@ export function RepoBlobPage() {
- +

{filepath}

{view && diff --git a/web/src/features/repos/ui/RepoRefsSection.tsx b/web/src/features/repos/ui/RepoRefsSection.tsx index 11b38acd75..07fdafa823 100644 --- a/web/src/features/repos/ui/RepoRefsSection.tsx +++ b/web/src/features/repos/ui/RepoRefsSection.tsx @@ -22,12 +22,12 @@ export function RepoRefsSection({ <>
- + {refs.head.ref} {refs.head.sha && ( - + {refs.head.sha.slice(0, 7)} )} @@ -36,13 +36,13 @@ export function RepoRefsSection({ )} - + {refs.branches.length}{" "} {refs.branches.length === 1 ? "branch" : "branches"} · - + {refs.tags.length} {refs.tags.length === 1 ? "tag" : "tags"}
diff --git a/web/src/features/repos/ui/ReposPage.tsx b/web/src/features/repos/ui/ReposPage.tsx index 72a972a4a9..c738f769aa 100644 --- a/web/src/features/repos/ui/ReposPage.tsx +++ b/web/src/features/repos/ui/ReposPage.tsx @@ -104,7 +104,7 @@ export function ReposPage() {

- Repositories + Repositories

{["a", "b", "c", "d", "e"].map((key) => ( @@ -131,7 +131,7 @@ export function ReposPage() {

- Repositories + Repositories

{/* Search + Sort bar */} From 9f121b0e2bff3adce5bfe84e585fbd5727622fa7 Mon Sep 17 00:00:00 2001 From: klopez4212 Date: Fri, 12 Jun 2026 15:26:04 +0100 Subject: [PATCH 3/3] Update narrow header menu spacing expectation --- desktop/tests/e2e/messaging.spec.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/desktop/tests/e2e/messaging.spec.ts b/desktop/tests/e2e/messaging.spec.ts index 610f174e00..825b53b0b5 100644 --- a/desktop/tests/e2e/messaging.spec.ts +++ b/desktop/tests/e2e/messaging.spec.ts @@ -751,8 +751,8 @@ test("narrow thread view collapses channel header actions into a menu", async ({ throw new Error("Expected header action menu and thread panel bounds"); } const menuGapPx = threadPanelBox.x - (menuBox.x + menuBox.width); - expect(menuGapPx).toBeGreaterThanOrEqual(10); - expect(menuGapPx).toBeLessThanOrEqual(14); + expect(menuGapPx).toBeGreaterThanOrEqual(22); + expect(menuGapPx).toBeLessThanOrEqual(26); await menuTrigger.click();