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
3 changes: 2 additions & 1 deletion apps/local-dashboard/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@
"tailwind-merge": "^3.5.0",
"tw-animate-css": "^1.4.0",
"vaul": "^1.1.2",
"zod": "^4.3.6"
"zod": "^4.3.6",
"@selftune/ui": "workspace:*"
},
"devDependencies": {
"@types/react": "^19.1.6",
Expand Down
4 changes: 2 additions & 2 deletions apps/local-dashboard/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@ import { AppSidebar } from "@/components/app-sidebar"
import { SiteHeader } from "@/components/site-header"
import { ThemeProvider } from "@/components/theme-provider"
import { SidebarInset, SidebarProvider } from "@/components/ui/sidebar"
import { TooltipProvider } from "@/components/ui/tooltip"
import { TooltipProvider } from "@selftune/ui/primitives"
import { Overview } from "@/pages/Overview"
import { SkillReport } from "@/pages/SkillReport"
import { Status } from "@/pages/Status"
import { useOverview } from "@/hooks/useOverview"
import type { SkillHealthStatus, SkillSummary } from "@/types"
import { deriveStatus, sortByPassRateAndChecks } from "@/utils"
import { deriveStatus, sortByPassRateAndChecks } from "@selftune/ui/lib"

const queryClient = new QueryClient({
defaultOptions: {
Expand Down
6 changes: 3 additions & 3 deletions apps/local-dashboard/src/components/app-sidebar.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { useEffect, useMemo, useState } from "react"
import { Link, useLocation } from "react-router-dom"
import { Badge } from "@/components/ui/badge"
import {
Badge,
Collapsible,
CollapsibleContent,
CollapsibleTrigger,
} from "@/components/ui/collapsible"
} from "@selftune/ui/primitives"
import { Input } from "@/components/ui/input"
import {
Sidebar,
Expand Down Expand Up @@ -35,7 +35,7 @@ import {
SearchIcon,
XCircleIcon,
} from "lucide-react"
import { formatRate } from "@/utils"
import { formatRate } from "@selftune/ui/lib"
import type { SkillHealthStatus } from "@/types"

interface SkillNavItem {
Expand Down
2 changes: 1 addition & 1 deletion apps/local-dashboard/src/components/theme-toggle.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { MoonIcon, SunIcon } from "lucide-react"
import { Button } from "@/components/ui/button"
import { Button } from "@selftune/ui/primitives"
import { useTheme } from "@/components/theme-provider"

export function ThemeToggle() {
Expand Down
2 changes: 1 addition & 1 deletion apps/local-dashboard/src/components/ui/sheet.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import * as React from "react"
import { Dialog as SheetPrimitive } from "@base-ui/react/dialog"

import { cn } from "@/lib/utils"
import { Button } from "@/components/ui/button"
import { Button } from "@selftune/ui/primitives"
import { XIcon } from "lucide-react"

function Sheet({ ...props }: SheetPrimitive.Root.Props) {
Expand Down
12 changes: 6 additions & 6 deletions apps/local-dashboard/src/components/ui/sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,12 @@ import { cva, type VariantProps } from "class-variance-authority"

import { useIsMobile } from "@/hooks/use-mobile"
import { cn } from "@/lib/utils"
import { Button } from "@/components/ui/button"
import {
Button,
Tooltip,
TooltipContent,
TooltipTrigger,
} from "@selftune/ui/primitives"
import { Input } from "@/components/ui/input"
import { Separator } from "@/components/ui/separator"
import {
Expand All @@ -16,11 +21,6 @@ import {
SheetTitle,
} from "@/components/ui/sheet"
import { Skeleton } from "@/components/ui/skeleton"
import {
Tooltip,
TooltipContent,
TooltipTrigger,
} from "@/components/ui/tooltip"
import { PanelLeftIcon } from "lucide-react"

const SIDEBAR_COOKIE_NAME = "sidebar_state"
Expand Down
45 changes: 2 additions & 43 deletions apps/local-dashboard/src/constants.tsx
Original file line number Diff line number Diff line change
@@ -1,43 +1,2 @@
import {
AlertTriangleIcon,
CheckCircleIcon,
CircleDotIcon,
HelpCircleIcon,
XCircleIcon,
} from "lucide-react";
import type { SkillHealthStatus } from "./types";

export const STATUS_CONFIG: Record<
SkillHealthStatus,
{
icon: React.ReactNode;
variant: "default" | "secondary" | "destructive" | "outline";
label: string;
}
> = {
HEALTHY: {
icon: <CheckCircleIcon className="size-4 text-emerald-600" />,
variant: "outline",
label: "Healthy",
},
WARNING: {
icon: <AlertTriangleIcon className="size-4 text-amber-500" />,
variant: "secondary",
label: "Warning",
},
CRITICAL: {
icon: <XCircleIcon className="size-4 text-red-500" />,
variant: "destructive",
label: "Critical",
},
UNGRADED: {
icon: <CircleDotIcon className="size-4 text-muted-foreground" />,
variant: "secondary",
label: "Ungraded",
},
UNKNOWN: {
icon: <HelpCircleIcon className="size-4 text-muted-foreground/60" />,
variant: "secondary",
label: "Unknown",
},
};
// Re-export from shared package
export { STATUS_CONFIG } from "@selftune/ui/lib";
7 changes: 1 addition & 6 deletions apps/local-dashboard/src/lib/utils.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1 @@
import { type ClassValue, clsx } from "clsx";
import { twMerge } from "tailwind-merge";

export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
export { cn } from "@selftune/ui/lib";
19 changes: 13 additions & 6 deletions apps/local-dashboard/src/pages/Overview.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
import { useMemo, useState } from "react"
import { ActivityPanel } from "@/components/ActivityTimeline"
import { OrchestrateRunsPanel } from "@/components/OrchestrateRunsPanel"
import { SectionCards } from "@/components/section-cards"
import { SkillHealthGrid } from "@/components/skill-health-grid"
import { Link } from "react-router-dom"
import {
ActivityPanel,
OrchestrateRunsPanel,
SectionCards,
SkillHealthGrid,
} from "@selftune/ui/components"
import type { UseQueryResult } from "@tanstack/react-query"
import type { SkillCard, SkillHealthStatus, SkillSummary, OverviewResponse } from "@/types"
import { useOrchestrateRuns } from "@/hooks/useOrchestrateRuns"
import { deriveStatus, sortByPassRateAndChecks } from "@/utils"
import { Skeleton } from "@/components/ui/skeleton"
import { Button } from "@/components/ui/button"
import { Button } from "@selftune/ui/primitives"
import { AlertCircleIcon, RefreshCwIcon, RocketIcon, LayersIcon, ActivityIcon, XIcon } from "lucide-react"

function deriveSkillCards(skills: SkillSummary[]): SkillCard[] {
Expand Down Expand Up @@ -200,7 +203,11 @@ export function Overview({
/>

<div className="grid grid-cols-1 gap-6 @5xl/main:grid-cols-[1fr_320px]">
<SkillHealthGrid cards={filteredCards} totalCount={cards.length} statusFilter={statusFilter} onStatusFilterChange={onStatusFilterChange} />
<SkillHealthGrid cards={filteredCards} totalCount={cards.length} statusFilter={statusFilter} onStatusFilterChange={onStatusFilterChange} renderSkillName={(skill) => (
<Link to={`/skills/${encodeURIComponent(skill.name)}`} className="text-sm font-medium hover:underline">
{skill.name}
</Link>
)} />

<div className="px-4 lg:px-6 @5xl/main:px-0 @5xl/main:pr-4 lg:@5xl/main:pr-6">
<div className="sticky top-4 space-y-4">
Expand Down
29 changes: 16 additions & 13 deletions apps/local-dashboard/src/pages/SkillReport.tsx
Original file line number Diff line number Diff line change
@@ -1,32 +1,35 @@
import { useEffect, useState } from "react"
import { Link, useParams } from "react-router-dom"
import { Badge } from "@/components/ui/badge"
import { Button } from "@/components/ui/button"
import {
Badge,
Button,
Card,
CardAction,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card"
import { Skeleton } from "@/components/ui/skeleton"
import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui/tabs"
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table"
import { EvolutionTimeline } from "@/components/EvolutionTimeline"
import { EvidenceViewer } from "@/components/EvidenceViewer"
import { InfoTip } from "@/components/InfoTip"
Tabs,
TabsList,
TabsTrigger,
TabsContent,
Tooltip,
TooltipContent,
TooltipTrigger,
} from "@selftune/ui/primitives"
import { Skeleton } from "@/components/ui/skeleton"
import { EvolutionTimeline } from "@selftune/ui/components"
import { EvidenceViewer } from "@selftune/ui/components"
import { InfoTip } from "@selftune/ui/components"
import { useSkillReport } from "@/hooks/useSkillReport"
import { STATUS_CONFIG } from "@/constants"
import { deriveStatus, formatRate, timeAgo } from "@/utils"
import { STATUS_CONFIG } from "@selftune/ui/lib"
import { deriveStatus, formatRate, timeAgo } from "@selftune/ui/lib"
import {
AlertCircleIcon,
ArrowLeftIcon,
Expand Down
10 changes: 5 additions & 5 deletions apps/local-dashboard/src/pages/Status.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
import { Badge } from "@/components/ui/badge"
import {
Badge,
Button,
Card,
CardAction,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card"
import { Button } from "@/components/ui/button"
} from "@selftune/ui/primitives"
import { Skeleton } from "@/components/ui/skeleton"
import { InfoTip } from "@/components/InfoTip"
import { InfoTip } from "@selftune/ui/components"
import { useDoctor } from "@/hooks/useDoctor"
import { timeAgo } from "@/utils"
import { timeAgo } from "@selftune/ui/lib"
import type { HealthCheck, HealthStatus } from "@/types"
import {
AlertCircleIcon,
Expand Down
35 changes: 14 additions & 21 deletions apps/local-dashboard/src/types.ts
Original file line number Diff line number Diff line change
@@ -1,40 +1,33 @@
/** Data contracts for the v2 SQLite-backed dashboard API */

// Re-export UI types from shared package
// Re-export dashboard contract types from shared package
export type {
CanonicalInvocation,
DoctorResult,
EvalSnapshot,
EvidenceEntry,
EvolutionEntry,
HealthCheck,
HealthStatus,
OrchestrateRunReport,
OrchestrateRunSkillAction,
PendingProposal,
SkillCard,
SkillHealthStatus,
UnmatchedQuery,
} from "@selftune/ui/types";

// Types that remain local (only used by pages/hooks, not by shared components)
export type {
CanonicalInvocation,
DoctorResult,
HealthCheck,
HealthStatus,
OrchestrateRunsResponse,
OverviewPayload,
OverviewResponse,
PendingProposal,
PromptSample,
SessionMeta,
SkillReportPayload,
SkillReportResponse,
SkillSummary,
SkillUsageRecord,
TelemetryRecord,
UnmatchedQuery,
} from "../../../cli/selftune/dashboard-contract";

// -- UI types -----------------------------------------------------------------

export type SkillHealthStatus = "HEALTHY" | "WARNING" | "CRITICAL" | "UNGRADED" | "UNKNOWN";

export interface SkillCard {
name: string;
scope: string | null;
passRate: number | null;
checks: number;
status: SkillHealthStatus;
hasEvidence: boolean;
uniqueSessions: number;
lastSeen: string | null;
}
37 changes: 2 additions & 35 deletions apps/local-dashboard/src/utils.ts
Original file line number Diff line number Diff line change
@@ -1,35 +1,2 @@
import type { SkillHealthStatus } from "./types";

export function deriveStatus(passRate: number, checks: number): SkillHealthStatus {
if (checks < 5) return "UNGRADED";
if (passRate >= 0.8) return "HEALTHY";
if (passRate >= 0.5) return "WARNING";
return "CRITICAL";
}

export function formatRate(rate: number | null | undefined): string {
if (rate === null || rate === undefined) return "--";
return `${Math.round(rate * 100)}%`;
}

export function sortByPassRateAndChecks<T extends { passRate: number | null; checks: number }>(
items: T[],
): T[] {
return [...items].sort((a, b) => {
const aRate = a.passRate ?? 1;
const bRate = b.passRate ?? 1;
if (aRate !== bRate) return aRate - bRate;
return b.checks - a.checks;
});
}

export function timeAgo(timestamp: string): string {
const diff = Date.now() - new Date(timestamp).getTime();
const mins = Math.floor(diff / 60000);
if (mins < 1) return "just now";
if (mins < 60) return `${mins}m ago`;
const hours = Math.floor(mins / 60);
if (hours < 24) return `${hours}h ago`;
const days = Math.floor(hours / 24);
return `${days}d ago`;
}
// Re-export utilities from shared package
export { deriveStatus, formatRate, sortByPassRateAndChecks, timeAgo } from "@selftune/ui/lib";
Loading