From 78f87d29a49dbd40c2260935915143df0afa3855 Mon Sep 17 00:00:00 2001 From: Gerard Kavanagh Date: Sat, 30 May 2026 19:53:25 +0100 Subject: [PATCH 1/2] feat(prd-142): wire Wave 0 "Is it working?" dashboard tiles (US-007) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add the five-tile "Is it working?" strip to the existing System Dashboard — no new route, no new page. Four tiles read the real Wave 0 endpoints (activation, mission success rate, error rate by subsystem, widget engagement); the fifth (per-primitive health, US-006) is deferred to Wave 3 and renders an explicit "not yet measured" placeholder rather than a fake green. - api-client: 4 typed getters + response interfaces over request() - use-analytics-api: 4 react-query hooks - is-it-working.tsx: new child section reusing the glass-card/motion tile pattern; loading + empty + error states, no hardcoded values - dashboard.tsx: render after --- frontend/components/dashboard/dashboard.tsx | 4 + .../components/dashboard/is-it-working.tsx | 180 ++++++++++++++++++ frontend/hooks/use-analytics-api.ts | 35 ++++ frontend/lib/api-client.ts | 49 +++++ 4 files changed, 268 insertions(+) create mode 100644 frontend/components/dashboard/is-it-working.tsx diff --git a/frontend/components/dashboard/dashboard.tsx b/frontend/components/dashboard/dashboard.tsx index af789f04c..4b2712bbc 100644 --- a/frontend/components/dashboard/dashboard.tsx +++ b/frontend/components/dashboard/dashboard.tsx @@ -56,6 +56,7 @@ const TimeDisplay = dynamic(() => Promise.resolve(() => { }), { ssr: false }); import { MetricCards } from './metric-cards' +import { IsItWorking } from './is-it-working' // import { ActivityFeed } from './activity-feed' // Hidden until endpoint implemented import { SystemHealth } from './system-health' import { QuickActions } from './quick-actions' @@ -116,6 +117,9 @@ export function Dashboard() { + {/* PRD-142 Wave 0 — "Is it working?" real measurement tiles */} + + {/* Main Content Grid */}
{/* Left Column - Charts and Analytics */} diff --git a/frontend/components/dashboard/is-it-working.tsx b/frontend/components/dashboard/is-it-working.tsx new file mode 100644 index 000000000..5f42ad687 --- /dev/null +++ b/frontend/components/dashboard/is-it-working.tsx @@ -0,0 +1,180 @@ +'use client' + +// PRD-142 Wave 0 (US-007) — the "Is it working?" answer strip. +// Five tiles over the real measurement endpoints shipped in Wave 0. The +// fifth tile (per-primitive health, US-006) has no honest data source yet +// and is deferred to Wave 3, so it renders as an explicit "not yet +// measured" placeholder rather than a fake green. + +import { motion } from 'framer-motion' +import { + Zap, + CheckCircle, + AlertTriangle, + MessageSquare, + Boxes, +} from 'lucide-react' +import { Card, CardContent } from '../ui/card' +import { Badge } from '../ui/badge' +import { + useActivationMetrics, + useMissionSuccessRate, + useErrorsBySubsystem, + useWidgetEngagement, +} from '../../hooks/use-analytics-api' + +interface TileProps { + icon: React.ComponentType<{ className?: string }> + label: string + value: React.ReactNode + sub: React.ReactNode + accent: string + isLoading?: boolean + isError?: boolean + muted?: boolean + delay?: number +} + +function Tile({ icon: Icon, label, value, sub, accent, isLoading, isError, muted, delay = 0 }: TileProps) { + return ( + + + +
+
+
+ +
+
+ {isLoading ? ( +
+
+
+
+ ) : ( + <> +
{isError ? '—' : value}
+
{label}
+ + )} +
+
+ {!isLoading && ( +
+ {isError ? 'Unavailable' : sub} +
+ )} +
+ + + + ) +} + +export function IsItWorking() { + const activation = useActivationMetrics() + const mission = useMissionSuccessRate() + const errors = useErrorsBySubsystem('24h') + const widget = useWidgetEngagement('7d') + + // --- Activation --- + const act = activation.data + const activationValue = act && act.total_workspaces > 0 ? `${Math.round(act.rate * 100)}%` : '0%' + const activationSub = act && act.total_workspaces > 0 + ? `${act.activated}/${act.total_workspaces} workspaces` + : 'No workspaces yet' + + // --- Mission success rate --- + const m = mission.data + const hasMissions = !!m && m.total_executions > 0 + const missionValue = hasMissions ? `${Math.round(m!.value)}%` : '0%' + const missionSub = hasMissions + ? `${m!.successful_executions}/${m!.total_executions} missions` + : 'No missions yet' + + // --- Error rate by subsystem --- + const e = errors.data + const errorTotal = e?.total ?? 0 + const worstSubsystem = e?.by_subsystem?.length + ? [...e.by_subsystem].sort((a, b) => b.count - a.count)[0] + : null + const errorSub = errorTotal > 0 && worstSubsystem + ? `${worstSubsystem.subsystem} (${worstSubsystem.count}) · 24h` + : 'No errors · 24h' + const errorAccent = errorTotal > 0 ? 'text-destructive' : 'text-success' + + // --- Widget engagement --- + const w = widget.data + const widgetSessions = w?.sessions ?? 0 + const widgetEvents = w?.by_event_type?.reduce((sum, ev) => sum + ev.count, 0) ?? 0 + const widgetSub = widgetSessions > 0 + ? `${widgetEvents} events · 7d` + : 'No widget activity' + + return ( +
+
+

Is it working?

+ + Live metrics + +
+ +
+ + + + + +
+
+ ) +} diff --git a/frontend/hooks/use-analytics-api.ts b/frontend/hooks/use-analytics-api.ts index cae12d429..e0811b1e6 100644 --- a/frontend/hooks/use-analytics-api.ts +++ b/frontend/hooks/use-analytics-api.ts @@ -1,5 +1,11 @@ import { useQuery, useMutation } from '@tanstack/react-query' import { apiClient } from '@/lib/api-client' +import type { + ActivationMetric, + MissionSuccessRateMetric, + ErrorsBySubsystemMetric, + WidgetEngagementMetric, +} from '@/lib/api-client' export const analyticsQueryKeys = { successRate: ['analytics', 'success-rate'], @@ -53,6 +59,35 @@ export function useAllMetrics() { }) } +// PRD-142 Wave 0 — "Is it working?" dashboard tiles +export function useActivationMetrics() { + return useQuery({ + queryKey: ['analytics', 'activation'], + queryFn: () => apiClient.getActivationMetrics() + }) +} + +export function useMissionSuccessRate() { + return useQuery({ + queryKey: ['analytics', 'mission-success-rate'], + queryFn: () => apiClient.getMissionSuccessRate() + }) +} + +export function useErrorsBySubsystem(window: string = '24h') { + return useQuery({ + queryKey: ['analytics', 'errors-by-subsystem', window], + queryFn: () => apiClient.getErrorsBySubsystem(window) + }) +} + +export function useWidgetEngagement(window: string = '7d') { + return useQuery({ + queryKey: ['analytics', 'widget-engagement', window], + queryFn: () => apiClient.getWidgetEngagement(window) + }) +} + export function useCostAnalysis() { return useQuery({ queryKey: ['analytics', 'cost-analysis'], diff --git a/frontend/lib/api-client.ts b/frontend/lib/api-client.ts index 44f6654b3..32b98618d 100644 --- a/frontend/lib/api-client.ts +++ b/frontend/lib/api-client.ts @@ -35,6 +35,38 @@ export interface RAGConfig { is_active?: boolean } +// PRD-142 Wave 0 — "Is it working?" dashboard tiles. Shapes mirror the +// analytics_real.py endpoints exactly; the request() helper returns the +// parsed JSON body directly (no ApiResponse envelope on these routes). +export interface ActivationMetric { + activated: number + total_workspaces: number + rate: number + generated_at: string +} + +export interface MissionSuccessRateMetric { + value: number + trend: number + total_executions: number + successful_executions: number + sources?: { workflows: number; missions: number } +} + +export interface ErrorsBySubsystemMetric { + window: string + total: number + by_subsystem: Array<{ subsystem: string; count: number; rate: number }> + generated_at: string +} + +export interface WidgetEngagementMetric { + window: string + by_event_type: Array<{ event_type: string; count: number }> + sessions: number + generated_at: string +} + // Mock configuration interface interface MockConfig { enabled: boolean @@ -2197,6 +2229,23 @@ class ApiClient { return this.request('/api/metrics/all') } + // ===== PRD-142 Wave 0 "Is it working?" tiles ===== + async getActivationMetrics(): Promise { + return this.request('/api/analytics/activation') + } + + async getMissionSuccessRate(): Promise { + return this.request('/api/analytics/dashboard/success-rate') + } + + async getErrorsBySubsystem(window: string = '24h'): Promise { + return this.request(`/api/analytics/errors/by-subsystem?window=${encodeURIComponent(window)}`) + } + + async getWidgetEngagement(window: string = '7d'): Promise { + return this.request(`/api/analytics/widget-engagement?window=${encodeURIComponent(window)}`) + } + async getAgentAnalytics(timeRange: string) { // Will use mock fallback automatically if endpoint fails return this.request(`/api/agents/analytics?timeRange=${timeRange}`) From aa5fe236295fa0e57fbe16467c00640a9abeeab5 Mon Sep 17 00:00:00 2001 From: Gerard Kavanagh Date: Sat, 30 May 2026 21:01:27 +0100 Subject: [PATCH 2/2] fix(prd-142): re-target Wave 0 "Is it working?" vitals to Command Centre US-007's vitals strip was wired into the orphaned /dashboard route. Move it to the live Command Centre surface (both variants) and delete the dead route. - Studio desktop: new IsItWorkingStrip (cc-stats idiom) rendered under StatsStrip in command-center-shell - Classic/mobile: second StatsBar vitals row in ActivityPage - Delete dead /dashboard route + dashboard.tsx, the glass-card is-it-working.tsx, and 4 orphaned children (metric-cards, system-health, quick-actions, performance-chart) - Fix dangling /dashboard refs: post-sign-in/up redirect -> /chat, drop the nav-highlight lines in main-layout and studio-menu Per-primitive health (US-006) stays an honest placeholder, deferred to Wave 3. --- frontend/app/dashboard/page.tsx | 15 - frontend/app/page.tsx | 2 - .../components/activity/activity-page.tsx | 73 +++ .../components/auth/user-profile-button.tsx | 4 +- .../command-center/command-center-shell.tsx | 2 + .../command-center/is-it-working-strip.tsx | 124 ++++ frontend/components/dashboard/dashboard.tsx | 421 -------------- .../components/dashboard/is-it-working.tsx | 180 ------ .../components/dashboard/metric-cards.tsx | 175 ------ .../dashboard/performance-chart.tsx | 331 ----------- .../components/dashboard/quick-actions.tsx | 539 ------------------ .../components/dashboard/system-health.tsx | 117 ---- frontend/components/layout/main-layout.tsx | 1 - frontend/lib/studio-menu.ts | 1 - 14 files changed, 201 insertions(+), 1784 deletions(-) delete mode 100644 frontend/app/dashboard/page.tsx create mode 100644 frontend/components/command-center/is-it-working-strip.tsx delete mode 100644 frontend/components/dashboard/dashboard.tsx delete mode 100644 frontend/components/dashboard/is-it-working.tsx delete mode 100644 frontend/components/dashboard/metric-cards.tsx delete mode 100644 frontend/components/dashboard/performance-chart.tsx delete mode 100644 frontend/components/dashboard/quick-actions.tsx delete mode 100644 frontend/components/dashboard/system-health.tsx diff --git a/frontend/app/dashboard/page.tsx b/frontend/app/dashboard/page.tsx deleted file mode 100644 index 1e8889dda..000000000 --- a/frontend/app/dashboard/page.tsx +++ /dev/null @@ -1,15 +0,0 @@ -'use client' - -import { MainLayout } from '@/components/layout/main-layout' -import { Dashboard } from '@/components/dashboard/dashboard' -import { usePageAPI } from '@/hooks/use-page-api' - -export default function DashboardPage() { - usePageAPI('dashboard') - - return ( - - - - ) -} diff --git a/frontend/app/page.tsx b/frontend/app/page.tsx index fd035fa7a..bb96691f3 100644 --- a/frontend/app/page.tsx +++ b/frontend/app/page.tsx @@ -2,7 +2,5 @@ import { redirect } from 'next/navigation' export default function Home() { // New home is Chat - // Keep this as a server redirect to avoid rendering dashboard on "/" - // and to make "/dashboard" the dedicated dashboard route. redirect('/chat') } diff --git a/frontend/components/activity/activity-page.tsx b/frontend/components/activity/activity-page.tsx index c673d7e99..7dbcf660a 100644 --- a/frontend/components/activity/activity-page.tsx +++ b/frontend/components/activity/activity-page.tsx @@ -15,6 +15,9 @@ import { Calendar, Rss, History, + Zap, + MessageSquare, + Boxes, } from 'lucide-react' import { Button } from '@/components/ui/button' import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select' @@ -25,6 +28,12 @@ import { ActivityFeed } from './activity-feed' import { CommandCenterHistory } from './command-center-history' import { ActivityCalendar } from './calendar' import { useActivityStats } from '@/hooks/use-activity-api' +import { + useActivationMetrics, + useMissionSuccessRate, + useErrorsBySubsystem, + useWidgetEngagement, +} from '@/hooks/use-analytics-api' import type { StatItem } from '@/components/shared/stats-bar' import { cn } from '@/lib/utils' @@ -128,6 +137,66 @@ export function ActivityPage() { { label: 'Needs Attention', value: liveStats?.needs_attention ?? 0, icon: AlertTriangle, iconColor: 'text-destructive' }, ] + // PRD-142 Wave 0 (US-007) — "Is it working?" platform vitals over the real + // measurement endpoints. Per-primitive health (US-006) has no honest data + // source yet and renders as an explicit placeholder, not a fake green. + const { data: activation } = useActivationMetrics() + const { data: mission } = useMissionSuccessRate() + const { data: errors } = useErrorsBySubsystem('24h') + const { data: widget } = useWidgetEngagement('7d') + + const totalWs = activation?.total_workspaces ?? 0 + const activationPct = totalWs > 0 ? Math.round((activation?.rate ?? 0) * 100) : 0 + + const missionTotal = mission?.total_executions ?? 0 + const missionPct = missionTotal > 0 ? Math.round(mission?.value ?? 0) : 0 + + const errorTotal = errors?.total ?? 0 + const worstSubsystem = errors?.by_subsystem?.length + ? [...errors.by_subsystem].sort((a, b) => b.count - a.count)[0] + : null + + const widgetSessions = widget?.sessions ?? 0 + const widgetEvents = widget?.by_event_type?.reduce((sum, ev) => sum + ev.count, 0) ?? 0 + + const vitals: StatItem[] = [ + { + label: 'Activation', + value: totalWs > 0 ? `${activationPct}%` : '—', + change: totalWs > 0 ? `${activation?.activated ?? 0}/${totalWs} workspaces` : 'No workspaces yet', + icon: Zap, + iconColor: 'text-primary', + }, + { + label: 'Mission success rate', + value: missionTotal > 0 ? `${missionPct}%` : '—', + change: missionTotal > 0 ? `${mission?.successful_executions ?? 0}/${missionTotal} missions` : 'No missions yet', + icon: CheckCircle2, + iconColor: 'text-[hsl(var(--success))]', + }, + { + label: 'Error rate by subsystem', + value: errorTotal, + change: errorTotal > 0 && worstSubsystem ? `${worstSubsystem.subsystem} (${worstSubsystem.count}) · 24h` : 'None · 24h', + icon: AlertTriangle, + iconColor: errorTotal > 0 ? 'text-destructive' : 'text-[hsl(var(--success))]', + }, + { + label: 'Widget engagement', + value: widgetSessions, + change: widgetSessions > 0 ? `${widgetEvents} events · 7d` : 'No widget activity', + icon: MessageSquare, + iconColor: 'text-[hsl(var(--info))]', + }, + { + label: 'Per-primitive health', + value: '—', + change: 'Not yet measured', + icon: Boxes, + iconColor: 'text-muted-foreground', + }, + ] + return (
@@ -164,6 +233,10 @@ export function ActivityPage() {
+
+ +
+
diff --git a/frontend/components/auth/user-profile-button.tsx b/frontend/components/auth/user-profile-button.tsx index a9ac0a90f..bc911163d 100644 --- a/frontend/components/auth/user-profile-button.tsx +++ b/frontend/components/auth/user-profile-button.tsx @@ -16,8 +16,8 @@ interface UserProfileButtonProps { } export function UserProfileButton({ - afterSignInUrl = '/dashboard', - afterSignUpUrl = '/dashboard', + afterSignInUrl = '/chat', + afterSignUpUrl = '/chat', }: UserProfileButtonProps) { const { isSignedIn, isLoaded } = useUser() diff --git a/frontend/components/command-center/command-center-shell.tsx b/frontend/components/command-center/command-center-shell.tsx index 9a4a4b668..ccb69825f 100644 --- a/frontend/components/command-center/command-center-shell.tsx +++ b/frontend/components/command-center/command-center-shell.tsx @@ -24,6 +24,7 @@ import { useActivitySchedule } from '@/hooks/use-activity-api' import { useDecisionsNeeded } from '@/hooks/use-kpi-api' import { StatsStrip } from './stats-strip' +import { IsItWorkingStrip } from './is-it-working-strip' import { SummaryTab } from './summary-tab' import { BoardTab } from './board-tab' import { CalendarTab } from './calendar-tab' @@ -138,6 +139,7 @@ export function CommandCenterShell() {
+