Skip to content
Open
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
31 changes: 11 additions & 20 deletions apps/dashboard/src/components/compare/compare-page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,7 @@ import { toast } from "@diffkit/ui/components/sonner";
import { useQuery, useQueryClient } from "@tanstack/react-query";
import { useRouter } from "@tanstack/react-router";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import {
DetailPageSkeletonLayout,
StaggerItem,
} from "#/components/details/detail-page";
import { DetailPageSkeletonLayout } from "#/components/details/detail-page";
import { createPullRequest } from "#/lib/github.functions";
import {
type GitHubQueryScope,
Expand Down Expand Up @@ -320,22 +317,16 @@ export function ComparePage({

function ComparePageSkeleton() {
return (
<DetailPageSkeletonLayout mainItemCount={3}>
<StaggerItem index={0}>
<div className="flex flex-col gap-3">
<div className="h-4 w-32 animate-pulse rounded bg-surface-1" />
<div className="h-7 w-3/5 animate-pulse rounded bg-surface-1" />
</div>
</StaggerItem>
<StaggerItem index={1}>
<div className="flex flex-col gap-3 rounded-lg border p-4">
<div className="h-9 w-full animate-pulse rounded bg-surface-1" />
<div className="h-32 w-full animate-pulse rounded bg-surface-1" />
</div>
</StaggerItem>
<StaggerItem index={2}>
<div className="h-40 w-full animate-pulse rounded-lg bg-surface-1" />
</StaggerItem>
<DetailPageSkeletonLayout>
<div className="flex flex-col gap-3">
<div className="h-4 w-32 animate-pulse rounded bg-surface-1" />
<div className="h-7 w-3/5 animate-pulse rounded bg-surface-1" />
</div>
<div className="flex flex-col gap-3 rounded-lg border p-4">
<div className="h-9 w-full animate-pulse rounded bg-surface-1" />
<div className="h-32 w-full animate-pulse rounded bg-surface-1" />
</div>
<div className="h-40 w-full animate-pulse rounded-lg bg-surface-1" />
</DetailPageSkeletonLayout>
);
}
171 changes: 32 additions & 139 deletions apps/dashboard/src/components/details/detail-page.tsx
Original file line number Diff line number Diff line change
@@ -1,123 +1,32 @@
import { Skeleton } from "@diffkit/ui/components/skeleton";
import { cn } from "@diffkit/ui/lib/utils";
import { Link } from "@tanstack/react-router";
import { animate, motion, useMotionValue, useTransform } from "motion/react";
import { createContext, useContext, useEffect, useState } from "react";

const STAGGER_DELAY = 1;
const ITEM_DURATION = 1.25;
const FADE_OUT_DURATION = 0.5;
const PAUSE_BEFORE_RESTART = 1;

type StaggerContextValue = {
cycle: number;
groupOpacity: ReturnType<typeof useMotionValue<number>>;
};
const defaultGroupOpacity = {
get: () => 1,
set: () => {},
} as unknown as ReturnType<typeof useMotionValue<number>>;
const StaggerCycleContext = createContext<StaggerContextValue>({
cycle: 0,
groupOpacity: defaultGroupOpacity,
});

function StaggerLoop({
itemCount,
children,
}: {
itemCount: number;
children: React.ReactNode;
}) {
const [cycle, setCycle] = useState(0);
const groupOpacity = useMotionValue(1);

// biome-ignore lint/correctness/useExhaustiveDependencies: cycle drives the restart loop
useEffect(() => {
const lastItemFinish = (itemCount - 1) * STAGGER_DELAY + ITEM_DURATION;
const totalVisible = lastItemFinish + PAUSE_BEFORE_RESTART;

const timeout = setTimeout(() => {
const controls = animate(groupOpacity, 0, {
duration: FADE_OUT_DURATION,
ease: "easeInOut",
onComplete: () => {
groupOpacity.set(1);
setCycle((c) => c + 1);
},
});
return () => controls.stop();
}, totalVisible * 1000);

return () => clearTimeout(timeout);
}, [cycle, itemCount, groupOpacity]);

return (
<StaggerCycleContext.Provider value={{ cycle, groupOpacity }}>
{children}
</StaggerCycleContext.Provider>
);
}

function StaggerItem({
children,
index,
className,
}: {
children: React.ReactNode;
index: number;
className?: string;
}) {
const { cycle, groupOpacity } = useContext(StaggerCycleContext);
const itemOpacity = useMotionValue(0);
const combinedOpacity = useTransform(
[itemOpacity, groupOpacity],
([item, group]) => Math.min(item as number, group as number),
);

// biome-ignore lint/correctness/useExhaustiveDependencies: cycle resets the item animation
useEffect(() => {
itemOpacity.set(0);
const controls = animate(itemOpacity, 1, {
type: "spring",
duration: ITEM_DURATION,
bounce: 0,
delay: index * STAGGER_DELAY,
});
return () => controls.stop();
}, [cycle, index, itemOpacity]);

return (
<motion.div
key={cycle}
initial={{ y: 8 }}
animate={{ y: 0 }}
transition={{
type: "spring",
duration: ITEM_DURATION,
bounce: 0,
delay: index * STAGGER_DELAY,
}}
style={{ opacity: combinedOpacity }}
className={className}
>
{children}
</motion.div>
);
}
import type { ReactNode } from "react";

type DetailHeaderIcon = React.ComponentType<{
size?: number;
strokeWidth?: number;
className?: string;
}>;

type DetailPageTitleProps = {
collectionHref: string;
collectionLabel: string;
owner: string;
repo: string;
number: number;
icon: DetailHeaderIcon;
iconClassName?: string;
title: string;
subtitle: ReactNode;
};

export function DetailPageLayout({
main,
sidebar,
}: {
main: React.ReactNode;
sidebar: React.ReactNode;
main: ReactNode;
sidebar: ReactNode;
}) {
return (
<div className="h-full overflow-auto">
Expand All @@ -139,17 +48,7 @@ export function DetailPageTitle({
iconClassName,
title,
subtitle,
}: {
collectionHref: string;
collectionLabel: string;
owner: string;
repo: string;
number: number;
icon: DetailHeaderIcon;
iconClassName?: string;
title: string;
subtitle: React.ReactNode;
}) {
}: DetailPageTitleProps) {
return (
<div className="flex flex-col gap-3">
<div className="flex items-center gap-1.5 text-xs text-muted-foreground">
Expand Down Expand Up @@ -184,36 +83,30 @@ export function DetailPageTitle({
);
}

export { StaggerItem };

export function DetailPageSkeletonLayout({
children,
mainItemCount,
sidebarSectionCount = 3,
}: {
children: React.ReactNode;
mainItemCount: number;
children: ReactNode;
sidebarSectionCount?: number;
}) {
Comment on lines 86 to 92
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find stale imports/usages of removed detail-page skeleton API.
rg -n --type=tsx -C2 'from "#/components/details/detail-page"'
rg -n --type=tsx -C2 '\bStaggerItem\b'
rg -n --type=tsx -C2 '<DetailPageSkeletonLayout[^>]*mainItemCount='

Repository: stylessh/diffkit

Length of output: 144


🏁 Script executed:

#!/bin/bash
# Search for imports from detail-page (use extension pattern instead of file type)
echo "=== Searching for imports from detail-page ==="
rg 'from.*detail-page' --include='*.tsx' --include='*.ts' -n

echo ""
echo "=== Searching for StaggerItem usage ==="
rg '\bStaggerItem\b' --include='*.tsx' --include='*.ts' -n

echo ""
echo "=== Searching for mainItemCount usage ==="
rg '\bmainItemCount\b' --include='*.tsx' --include='*.ts' -n

echo ""
echo "=== Checking detail-page.tsx exports ==="
cat apps/dashboard/src/components/details/detail-page.tsx | head -100

Repository: stylessh/diffkit

Length of output: 4094


🏁 Script executed:

#!/bin/bash
# Get full detail-page.tsx file
echo "=== Full detail-page.tsx ==="
wc -l apps/dashboard/src/components/details/detail-page.tsx

# Read the full file
cat apps/dashboard/src/components/details/detail-page.tsx

echo ""
echo "=== workflow-job-page.tsx imports (first 50 lines) ==="
head -50 apps/dashboard/src/components/workflows/workflow-job-page.tsx

echo ""
echo "=== workflow-run-page.tsx imports (first 50 lines) ==="
head -50 apps/dashboard/src/components/workflows/workflow-run-page.tsx

# Search using simpler grep patterns
echo ""
echo "=== StaggerItem mentions in tsx/ts files ==="
find apps/dashboard/src -type f \( -name "*.tsx" -o -name "*.ts" \) -exec grep -l "StaggerItem" {} \;

echo ""
echo "=== mainItemCount mentions in tsx/ts files ==="
find apps/dashboard/src -type f \( -name "*.tsx" -o -name "*.ts" \) -exec grep -l "mainItemCount" {} \;

Repository: stylessh/diffkit

Length of output: 6903


🏁 Script executed:

#!/bin/bash
# Check StaggerItem and mainItemCount usage in workflow-job-page.tsx
echo "=== workflow-job-page.tsx: lines 234-260 ==="
sed -n '234,260p' apps/dashboard/src/components/workflows/workflow-job-page.tsx

echo ""
echo "=== workflow-job-page.tsx: StaggerItem usage ==="
grep -n "StaggerItem\|mainItemCount" apps/dashboard/src/components/workflows/workflow-job-page.tsx | head -20

echo ""
echo "=== workflow-run-page.tsx: lines 166-200 ==="
sed -n '166,200p' apps/dashboard/src/components/workflows/workflow-run-page.tsx

echo ""
echo "=== workflow-run-page.tsx: StaggerItem usage ==="
grep -n "StaggerItem\|mainItemCount" apps/dashboard/src/components/workflows/workflow-run-page.tsx | head -20

Repository: stylessh/diffkit

Length of output: 2251


Breaking API change requires fixes in consuming modules.

DetailPageSkeletonLayout no longer accepts mainItemCount parameter and no longer exports StaggerItem. However, both are still imported and used in:

  • workflow-job-page.tsx (line 20 imports StaggerItem; line 236 passes mainItemCount={2}; lines 237–253 render <StaggerItem> components)
  • workflow-run-page.tsx (line 12 imports StaggerItem; line 168 passes mainItemCount={3}; lines 169–190 render <StaggerItem> components)

These imports and usages will fail type-check and build until both files are updated to use the new sidebarSectionCount prop instead and remove StaggerItem wrapping.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/dashboard/src/components/details/detail-page.tsx` around lines 86 - 92,
DetailPageSkeletonLayout's API changed: it no longer accepts mainItemCount nor
exports StaggerItem; update consuming modules that import StaggerItem and pass
mainItemCount to instead pass the value as sidebarSectionCount to
DetailPageSkeletonLayout and remove any StaggerItem usage/wrapping.
Specifically, drop the StaggerItem import/JSX and directly render the children
previously wrapped by <StaggerItem>, and replace occurrences like
mainItemCount={N} on DetailPageSkeletonLayout with sidebarSectionCount={N};
update any references to the removed StaggerItem symbol accordingly so
type-checking/build succeed.

const totalItems = Math.max(mainItemCount, sidebarSectionCount);
return (
<StaggerLoop itemCount={totalItems}>
<div className="h-full overflow-auto">
<div className="mx-auto grid max-w-7xl gap-16 px-3 py-10 md:px-6 xl:grid-cols-[minmax(0,1fr)_minmax(16rem,20rem)]">
<div className="flex min-w-0 flex-col gap-8">{children}</div>
<aside className="hidden h-fit flex-col gap-6 xl:sticky xl:top-10 xl:flex">
{Array.from({ length: sidebarSectionCount }, (_, i) => (
<div className="h-full overflow-auto">
<div className="mx-auto grid max-w-7xl gap-16 px-3 py-10 md:px-6 xl:grid-cols-[minmax(0,1fr)_minmax(16rem,20rem)]">
<div className="flex min-w-0 flex-col gap-8">{children}</div>
<aside className="hidden h-fit flex-col gap-6 xl:sticky xl:top-10 xl:flex">
{Array.from({ length: sidebarSectionCount }, (_, i) => (
<div
// biome-ignore lint/suspicious/noArrayIndexKey: static skeleton items, order never changes
<StaggerItem key={i} index={i}>
<div className="flex flex-col gap-2.5">
<Skeleton className="h-4 w-24 rounded-md" />
<Skeleton className="h-4 w-full rounded-md" />
</div>
</StaggerItem>
))}
</aside>
</div>
key={i}
className="flex flex-col gap-2.5"
>
<Skeleton className="h-4 w-24 rounded-md" />
<Skeleton className="h-4 w-full rounded-md" />
</div>
))}
</aside>
</div>
</StaggerLoop>
</div>
);
}
113 changes: 52 additions & 61 deletions apps/dashboard/src/components/issues/detail/issue-detail-page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import { useMemo } from "react";
import {
DetailPageLayout,
DetailPageSkeletonLayout,
StaggerItem,
} from "#/components/details/detail-page";
import {
githubIssuePageQueryOptions,
Expand Down Expand Up @@ -154,77 +153,69 @@ export function IssueDetailContent({

function IssueDetailPageSkeleton() {
return (
<DetailPageSkeletonLayout mainItemCount={4}>
<StaggerItem index={0}>
<div className="flex flex-col gap-3">
<Skeleton className="h-4 w-48 rounded-md" />
<div className="flex items-start gap-3">
<Skeleton className="mt-1 size-5 rounded-full" />
<div className="flex min-w-0 flex-1 flex-col gap-2">
<Skeleton className="h-8 w-3/4 rounded-md" />
<div className="flex gap-2">
<Skeleton className="h-5 w-16 rounded-full" />
<Skeleton className="h-4 w-48" />
</div>
<DetailPageSkeletonLayout>
<div className="flex flex-col gap-3">
<Skeleton className="h-4 w-48 rounded-md" />
<div className="flex items-start gap-3">
<Skeleton className="mt-1 size-5 rounded-full" />
<div className="flex min-w-0 flex-1 flex-col gap-2">
<Skeleton className="h-8 w-3/4 rounded-md" />
<div className="flex gap-2">
<Skeleton className="h-5 w-16 rounded-full" />
<Skeleton className="h-4 w-48" />
</div>
</div>
</div>
</StaggerItem>
</div>

<StaggerItem index={1}>
<div className="flex flex-wrap gap-1.5">
<Skeleton className="h-6 w-20 rounded-full" />
<Skeleton className="h-6 w-24 rounded-full" />
</div>
</StaggerItem>
<div className="flex flex-wrap gap-1.5">
<Skeleton className="h-6 w-20 rounded-full" />
<Skeleton className="h-6 w-24 rounded-full" />
</div>

<StaggerItem index={2}>
<div className="rounded-lg border bg-surface-0 p-5">
<div className="flex flex-col gap-3">
<Skeleton className="h-4 w-full rounded-md" />
<Skeleton className="h-4 w-5/6 rounded-md" />
<Skeleton className="h-4 w-2/3 rounded-md" />
</div>
<div className="rounded-lg border bg-surface-0 p-5">
<div className="flex flex-col gap-3">
<Skeleton className="h-4 w-full rounded-md" />
<Skeleton className="h-4 w-5/6 rounded-md" />
<Skeleton className="h-4 w-2/3 rounded-md" />
</div>
</StaggerItem>
</div>

<StaggerItem index={3}>
<div className="flex flex-col gap-6">
<div className="flex items-center justify-between gap-2 rounded-lg bg-surface-1 px-4 py-2.5">
<Skeleton className="h-4 w-14 rounded-md" />
<Skeleton className="h-4 w-6 rounded-md" />
</div>
<div className="flex flex-col gap-5 pl-8">
{/* Comment */}
<div className="flex items-start gap-2">
<Skeleton className="size-5 rounded-full" />
<div className="flex flex-col gap-1.5">
<Skeleton className="h-3.5 w-36 rounded-md" />
<Skeleton className="h-12 w-64 rounded-lg" />
</div>
</div>
{/* Label event */}
<div className="flex items-center gap-2">
<Skeleton className="size-4 rounded" />
<Skeleton className="h-3.5 w-32 rounded-md" />
<Skeleton className="h-5 w-16 rounded-full" />
</div>
{/* Comment */}
<div className="flex items-start gap-2">
<Skeleton className="size-5 rounded-full" />
<div className="flex flex-col gap-1.5">
<Skeleton className="h-3.5 w-44 rounded-md" />
<Skeleton className="h-3.5 w-56 rounded-md" />
</div>
<div className="flex flex-col gap-6">
<div className="flex items-center justify-between gap-2 rounded-lg bg-surface-1 px-4 py-2.5">
<Skeleton className="h-4 w-14 rounded-md" />
<Skeleton className="h-4 w-6 rounded-md" />
</div>
<div className="flex flex-col gap-5 pl-8">
{/* Comment */}
<div className="flex items-start gap-2">
<Skeleton className="size-5 rounded-full" />
<div className="flex flex-col gap-1.5">
<Skeleton className="h-3.5 w-36 rounded-md" />
<Skeleton className="h-12 w-64 rounded-lg" />
</div>
{/* Assignment */}
<div className="flex items-center gap-2">
<Skeleton className="size-4 rounded" />
<Skeleton className="h-3.5 w-40 rounded-md" />
</div>
{/* Label event */}
<div className="flex items-center gap-2">
<Skeleton className="size-4 rounded" />
<Skeleton className="h-3.5 w-32 rounded-md" />
<Skeleton className="h-5 w-16 rounded-full" />
</div>
{/* Comment */}
<div className="flex items-start gap-2">
<Skeleton className="size-5 rounded-full" />
<div className="flex flex-col gap-1.5">
<Skeleton className="h-3.5 w-44 rounded-md" />
<Skeleton className="h-3.5 w-56 rounded-md" />
</div>
</div>
{/* Assignment */}
<div className="flex items-center gap-2">
<Skeleton className="size-4 rounded" />
<Skeleton className="h-3.5 w-40 rounded-md" />
</div>
</div>
</StaggerItem>
</div>
</DetailPageSkeletonLayout>
);
}
Loading
Loading