Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
3d3a923
feat(dashboard): render StatCard trend and sparkline slot
pratyush618 May 5, 2026
335dc9d
feat(dashboard): add TableSkeleton primitive
pratyush618 May 5, 2026
61d0b13
fix(dashboard): show error state on overview stats and throughput
pratyush618 May 5, 2026
2bff6b8
fix(dashboard): toast on successful setting deletion
pratyush618 May 5, 2026
9862bf5
feat(dashboard): add accent left-bar to active sidebar link
pratyush618 May 5, 2026
3559677
refactor(dashboard): bump page-header title to 2xl
pratyush618 May 5, 2026
b621fb2
feat(dashboard): retrigger fade-in on every route change
pratyush618 May 5, 2026
e5e14bb
feat(dashboard): add top progress bar driven by query activity
pratyush618 May 5, 2026
b6cba77
feat(dashboard): show trends and sparklines on overview stats
pratyush618 May 5, 2026
25d7d28
fix(dashboard): use EmptyState on overview tables
pratyush618 May 5, 2026
2e3bfce
feat(dashboard): add hover tooltip to throughput sparkline
pratyush618 May 5, 2026
acbec8d
feat(dashboard): hide jobs error column when none on page
pratyush618 May 5, 2026
38289b6
feat(dashboard): fade tab content when switching
pratyush618 May 5, 2026
7d5e832
refactor(dashboard): widen job overview label column to 140px
pratyush618 May 5, 2026
ad0eb69
feat(metrics): include p50/p95/p99 in timeseries buckets
pratyush618 May 5, 2026
977d816
feat(dashboard): plot latency percentiles with brush and toggle
pratyush618 May 5, 2026
63899ae
feat(dashboard): add brush and refresh anim to throughput chart
pratyush618 May 5, 2026
1545249
feat(dashboard): group metrics columns by volume and latency
pratyush618 May 5, 2026
d8a3a27
feat(dashboard): pulse worker heartbeat dot when active
pratyush618 May 5, 2026
3965b37
fix(dashboard): use EmptyState on system tables
pratyush618 May 5, 2026
5332527
feat(dashboard): hover state on dead-letter rows
pratyush618 May 5, 2026
58b15af
refactor(dashboard): use TableSkeleton on queues and breakers
pratyush618 May 5, 2026
d9352ca
feat(dashboard): add stats banners to queues and workers pages
pratyush618 May 5, 2026
babbf20
fix(dashboard): disable chart re-animation on every poll
pratyush618 May 5, 2026
77465cd
refactor(dashboard): scope top progress bar to mutations only
pratyush618 May 5, 2026
b0c9fc8
fix(dashboard): match system stats response shapes
pratyush618 May 5, 2026
d99f2e6
feat(dashboard): move refresh interval control to settings
pratyush618 May 5, 2026
5e1b434
refactor: extract _ActiveContext to break context cycle
pratyush618 May 5, 2026
14eac01
refactor(cli): hoist top-level imports
pratyush618 May 5, 2026
d50a73f
refactor(app): hoist context and interception imports
pratyush618 May 5, 2026
82a971c
refactor(task): hoist canvas and interception imports
pratyush618 May 5, 2026
5bb9fe9
refactor(testing): hoist resource and context imports
pratyush618 May 5, 2026
e6164e4
refactor(locks): hoist time import
pratyush618 May 5, 2026
f8c52bc
refactor(mixins): hoist time in inspection
pratyush618 May 5, 2026
2c33619
refactor(prefork): hoist context imports
pratyush618 May 5, 2026
0f85033
refactor(proxies): hoist signing and schema imports
pratyush618 May 5, 2026
c664c67
refactor(resources): hoist runtime imports
pratyush618 May 5, 2026
6485132
refactor(interception): hoist stdlib imports
pratyush618 May 5, 2026
84b3009
refactor(workflows): hoist analysis and viz imports
pratyush618 May 5, 2026
e731df3
refactor(async_support): hoist context and lock imports
pratyush618 May 5, 2026
541abbb
refactor(contrib): hoist stdlib imports
pratyush618 May 5, 2026
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: 5 additions & 1 deletion dashboard/src/components/layout/app-shell.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,25 @@
import { useLocation } from "@tanstack/react-router";
import type { ReactNode } from "react";
import { TooltipProvider } from "@/components/ui";
import { useApplyAccent } from "@/features/settings";
import { CommandPalette } from "./command-palette";
import { Header } from "./header";
import { RouteErrorBoundary } from "./route-error-boundary";
import { Sidebar } from "./sidebar";
import { TopProgressBar } from "./top-progress-bar";

export function AppShell({ children }: { children: ReactNode }) {
useApplyAccent();
const { pathname } = useLocation();
return (
<TooltipProvider delayDuration={300}>
<div className="flex min-h-screen bg-[var(--bg)] text-[var(--fg)]">
<Sidebar />
<div className="flex min-w-0 flex-1 flex-col">
<Header />
<TopProgressBar />
<main className="flex-1 px-4 py-5 md:px-6 md:py-6">
<div className="mx-auto max-w-[1400px] animate-fade-in">
<div key={pathname} className="mx-auto max-w-[1400px] animate-fade-in">
<RouteErrorBoundary>{children}</RouteErrorBoundary>
</div>
</main>
Expand Down
2 changes: 0 additions & 2 deletions dashboard/src/components/layout/header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { Button, Kbd } from "@/components/ui";
import { useCommandPalette } from "@/providers";
import { LastRefreshed } from "./last-refreshed";
import { MobileMenu } from "./mobile-menu";
import { RefreshControl } from "./refresh-control";
import { ThemeToggle } from "./theme-toggle";

export function Header() {
Expand Down Expand Up @@ -37,7 +36,6 @@ export function Header() {
</Button>
<div className="ml-auto flex items-center gap-3">
<LastRefreshed className="hidden sm:inline-flex" />
<RefreshControl />
<ThemeToggle />
</div>
</header>
Expand Down
2 changes: 1 addition & 1 deletion dashboard/src/components/layout/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export { Header } from "./header";
export { LastRefreshed } from "./last-refreshed";
export { MobileMenu } from "./mobile-menu";
export { PageHeader } from "./page-header";
export { RefreshControl } from "./refresh-control";
export { RouteErrorBoundary } from "./route-error-boundary";
export { Sidebar } from "./sidebar";
export { ThemeToggle } from "./theme-toggle";
export { TopProgressBar } from "./top-progress-bar";
6 changes: 4 additions & 2 deletions dashboard/src/components/layout/page-header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,10 @@ export function PageHeader({
{eyebrow}
</div>
) : null}
<h1 className="text-xl font-semibold tracking-tight text-[var(--fg)]">{title}</h1>
{description ? <p className="mt-1 text-sm text-[var(--fg-muted)]">{description}</p> : null}
<h1 className="text-2xl font-semibold tracking-tight text-[var(--fg)]">{title}</h1>
{description ? (
<p className="mt-1.5 text-sm text-[var(--fg-muted)]">{description}</p>
) : null}
</div>
{actions ? <div className="flex flex-wrap items-center gap-2">{actions}</div> : null}
</div>
Expand Down
35 changes: 0 additions & 35 deletions dashboard/src/components/layout/refresh-control.tsx

This file was deleted.

11 changes: 9 additions & 2 deletions dashboard/src/components/layout/sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -91,13 +91,20 @@ export function Sidebar() {
<li key={to}>
<Link
to={to}
aria-current={active ? "page" : undefined}
className={cn(
"flex items-center gap-2.5 rounded-md px-2 py-1.5 text-sm transition-colors",
"relative flex items-center gap-2.5 rounded-md px-2 py-1.5 text-sm transition-colors",
active
? "bg-[var(--surface-2)] text-[var(--fg)] shadow-xs"
: "text-[var(--fg-muted)] hover:bg-[var(--surface-2)] hover:text-[var(--fg)]",
: "text-[var(--fg-muted)] hover:bg-[var(--surface-2)]/60 hover:text-[var(--fg)]",
)}
>
{active ? (
<span
aria-hidden
className="absolute -left-3 inset-y-1.5 w-0.5 rounded-r-full bg-accent"
/>
) : null}
<Icon className={cn("size-4", active && "text-accent")} aria-hidden />
<span>{label}</span>
</Link>
Expand Down
36 changes: 36 additions & 0 deletions dashboard/src/components/layout/top-progress-bar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { useIsMutating } from "@tanstack/react-query";
import { useEffect, useState } from "react";

/**
* Thin accent-colored bar that fades in while a mutation (user action)
* is in flight. Polling refetches do not trigger it — at low polling
* intervals a per-fetch indicator strobes and reads as visual jitter.
* The "Updated just now" label in the header already conveys refresh
* cadence, so the progress bar focuses on intentional state changes.
*/
export function TopProgressBar() {
const mutating = useIsMutating();
const busy = mutating > 0;
const [visible, setVisible] = useState(false);

useEffect(() => {
if (busy) {
setVisible(true);
return;
}
const timeout = window.setTimeout(() => setVisible(false), 400);
return () => window.clearTimeout(timeout);
}, [busy]);

return (
<div aria-hidden className="sticky top-14 z-10 h-0.5 overflow-hidden bg-transparent">
<div
className={
visible
? "h-full w-full bg-accent/70 transition-opacity duration-150 opacity-100"
: "h-full w-full bg-accent/70 transition-opacity duration-300 opacity-0"
}
/>
</div>
);
}
2 changes: 1 addition & 1 deletion dashboard/src/components/ui/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ export {
SheetTitle,
SheetTrigger,
} from "./sheet";
export { Skeleton } from "./skeleton";
export { Skeleton, TableSkeleton } from "./skeleton";
export { StatCard } from "./stat-card";
export {
Table,
Expand Down
55 changes: 55 additions & 0 deletions dashboard/src/components/ui/skeleton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,58 @@ export function Skeleton({ className, ...props }: HTMLAttributes<HTMLDivElement>
<div className={cn("animate-shimmer rounded-md bg-[var(--surface-2)]", className)} {...props} />
);
}

interface TableSkeletonProps {
rows?: number;
columns?: Array<string | number>;
className?: string;
}

/**
* Table-shaped skeleton — gives a placeholder that matches the eventual row
* grid instead of a single opaque block. Pass widths per column (Tailwind
* class like "w-32" or numeric ch units).
*/
export function TableSkeleton({
rows = 8,
columns = ["w-24", "w-40", "w-20", "w-16", "w-28", "w-24"],
className,
}: TableSkeletonProps) {
return (
<div
className={cn(
"rounded-lg bg-[var(--surface)] ring-1 ring-inset ring-[var(--border)] shadow-xs",
className,
)}
>
<div className="flex items-center gap-6 border-b border-[var(--border)] px-4 py-3">
{columns.map((width, idx) => (
<Skeleton
// biome-ignore lint/suspicious/noArrayIndexKey: skeleton order is stable
key={`head-${idx}`}
className={cn("h-3", typeof width === "number" ? undefined : width)}
style={typeof width === "number" ? { width: `${width}ch` } : undefined}
/>
))}
</div>
<div className="divide-y divide-[var(--border)]">
{Array.from({ length: rows }, (_, rowIdx) => (
<div
// biome-ignore lint/suspicious/noArrayIndexKey: skeleton order is stable
key={`row-${rowIdx}`}
className="flex items-center gap-6 px-4 py-3"
>
{columns.map((width, colIdx) => (
<Skeleton
// biome-ignore lint/suspicious/noArrayIndexKey: skeleton order is stable
key={`cell-${rowIdx}-${colIdx}`}
className={cn("h-4", typeof width === "number" ? undefined : width)}
style={typeof width === "number" ? { width: `${width}ch` } : undefined}
/>
))}
</div>
))}
</div>
</div>
);
}
57 changes: 57 additions & 0 deletions dashboard/src/components/ui/stat-card-trend.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { describe, expect, it } from "vitest";
import { computeTrend, trendToneClass } from "./stat-card-trend";

describe("trendToneClass", () => {
it("renders flat as muted regardless of upIsGood", () => {
expect(trendToneClass("flat", true)).toMatch(/fg-subtle/);
expect(trendToneClass("flat", false)).toMatch(/fg-subtle/);
});

it("treats up as success when upIsGood", () => {
expect(trendToneClass("up", true)).toBe("text-success");
expect(trendToneClass("down", true)).toBe("text-danger");
});

it("inverts when upIsGood is false", () => {
expect(trendToneClass("up", false)).toBe("text-danger");
expect(trendToneClass("down", false)).toBe("text-success");
});
});

describe("computeTrend", () => {
it("returns null on non-finite input", () => {
expect(computeTrend(Number.NaN, 5)).toBeNull();
expect(computeTrend(5, Number.POSITIVE_INFINITY)).toBeNull();
});

it("returns flat when current and previous are both zero", () => {
expect(computeTrend(0, 0)).toEqual({ direction: "flat", label: "0%", upIsGood: undefined });
});

it("returns 'new' when previous is zero and current is non-zero", () => {
const t = computeTrend(7, 0);
expect(t?.direction).toBe("up");
expect(t?.label).toBe("new");
});

it("computes positive percentage", () => {
expect(computeTrend(120, 100)).toEqual({ direction: "up", label: "+20%", upIsGood: undefined });
});

it("computes negative percentage", () => {
expect(computeTrend(80, 100)).toEqual({
direction: "down",
label: "-20%",
upIsGood: undefined,
});
});

it("returns flat when rounded percentage is zero", () => {
expect(computeTrend(1001, 1000)?.direction).toBe("flat");
});

it("threads upIsGood through", () => {
const t = computeTrend(120, 100, { upIsGood: false });
expect(t?.upIsGood).toBe(false);
});
});
44 changes: 44 additions & 0 deletions dashboard/src/components/ui/stat-card-trend.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
export type TrendDirection = "up" | "down" | "flat";

export interface StatTrend {
direction: TrendDirection;
/** Display label (e.g. "12%", "+4 / hr"). Caller controls formatting. */
label: string;
/**
* Whether `up` should read as positive. Defaults to true. Set false for
* metrics where rising is bad (e.g. failures, latency) so colors invert.
*/
upIsGood?: boolean;
}

export function trendToneClass(direction: TrendDirection, upIsGood: boolean): string {
if (direction === "flat") return "text-[var(--fg-subtle)]";
const positive = direction === "up" ? upIsGood : !upIsGood;
return positive ? "text-success" : "text-danger";
}

/**
* Compare current vs previous bucket totals and produce a trend descriptor.
* Returns `null` when there is no previous data to compare against — the
* caller should treat that as "no trend yet" rather than rendering "flat".
*/
export function computeTrend(
current: number,
previous: number,
options: { upIsGood?: boolean } = {},
): StatTrend | null {
if (!Number.isFinite(current) || !Number.isFinite(previous)) return null;
if (previous === 0 && current === 0)
return { direction: "flat", label: "0%", upIsGood: options.upIsGood };
if (previous === 0) {
return { direction: "up", label: "new", upIsGood: options.upIsGood };
}
const delta = current - previous;
const pct = Math.round((delta / previous) * 100);
if (pct === 0) return { direction: "flat", label: "0%", upIsGood: options.upIsGood };
return {
direction: pct > 0 ? "up" : "down",
label: `${pct > 0 ? "+" : ""}${pct}%`,
upIsGood: options.upIsGood,
};
}
Loading
Loading