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
15 changes: 0 additions & 15 deletions frontend/app/dashboard/page.tsx

This file was deleted.

2 changes: 0 additions & 2 deletions frontend/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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')
}
73 changes: 73 additions & 0 deletions frontend/components/activity/activity-page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand All @@ -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'

Expand Down Expand Up @@ -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',
},
]
Comment on lines +143 to +198
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 | 🟠 Major | ⚡ Quick win

Don't present analytics outages as empty-state copy.

These hooks ignore query status, so a backend failure produces the same strings as real empty data (No workspaces yet, None · 24h, No widget activity). That makes the vitals bar look healthy/empty when the source is actually unavailable. Gate the vitals copy on isLoading/isError before falling back to zero-data messaging.

🐛 Sketch of the fix
-  const { data: activation } = useActivationMetrics()
-  const { data: mission } = useMissionSuccessRate()
-  const { data: errors } = useErrorsBySubsystem('24h')
-  const { data: widget } = useWidgetEngagement('7d')
+  const activationQuery = useActivationMetrics()
+  const missionQuery = useMissionSuccessRate()
+  const errorsQuery = useErrorsBySubsystem('24h')
+  const widgetQuery = useWidgetEngagement('7d')
+
+  const { data: activation } = activationQuery
+  const { data: mission } = missionQuery
+  const { data: errors } = errorsQuery
+  const { data: widget } = widgetQuery
+  const metricsUnavailable = [activationQuery, missionQuery, errorsQuery, widgetQuery].some(
+    (query) => query.isError,
+  )

Then render neutral "Loading metrics…" / "Metrics unavailable" copy for the vitals cards instead of the current zero-data strings whenever the queries are not ready.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/components/activity/activity-page.tsx` around lines 143 - 198, The
vitals cards currently treat failed or loading queries the same as valid empty
data because the hooks (useActivationMetrics, useMissionSuccessRate,
useErrorsBySubsystem, useWidgetEngagement) are only reading .data; update each
hook usage to also read their isLoading/isError flags and, when isLoading show
neutral "Loading metrics…" and when isError show "Metrics unavailable" in the
vitals entries instead of the zero-data strings (e.g. replace "No workspaces
yet", "None · 24h", "No widget activity" with the new messages); ensure the
logic that computes totalWs/activationPct, missionTotal/missionPct,
errorTotal/worstSubsystem, and widgetSessions/widgetEvents uses the presence of
isLoading/isError to choose those fallback strings and not compute numeric
fallbacks when queries are not ready (you can centralize into a small helper
used when building the vitals array).


return (
<div className="space-y-4 sm:space-y-6">
<div data-tour="activity-page-header">
Expand Down Expand Up @@ -164,6 +233,10 @@ export function ActivityPage() {
<StatsBar stats={stats} className="grid gap-3 md:gap-4" />
</div>

<div data-tour="activity-vitals">
<StatsBar stats={vitals} glow={false} className="grid gap-3 md:gap-4 lg:grid-cols-5" />
</div>

<div>
<FilterTabs tabs={TAB_DEFS} value={activeTab} onValueChange={setActiveTab} dataTour="activity-tabs">
<TabsContent value="summary">
Expand Down
4 changes: 2 additions & 2 deletions frontend/components/auth/user-profile-button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ interface UserProfileButtonProps {
}

export function UserProfileButton({
afterSignInUrl = '/dashboard',
afterSignUpUrl = '/dashboard',
afterSignInUrl = '/chat',
afterSignUpUrl = '/chat',
}: UserProfileButtonProps) {
const { isSignedIn, isLoaded } = useUser()

Expand Down
2 changes: 2 additions & 0 deletions frontend/components/command-center/command-center-shell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -138,6 +139,7 @@ export function CommandCenterShell() {
</div>

<StatsStrip />
<IsItWorkingStrip />

<nav className="cc-tabs" aria-label="Command Centre sections">
{TABS.map((t) => {
Expand Down
124 changes: 124 additions & 0 deletions frontend/components/command-center/is-it-working-strip.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
'use client'

/**
* IsItWorkingStrip — PRD-142 Wave 0 (US-007). A second cc-stats strip,
* rendered under StatsStrip in the Command Centre, answering "is the
* platform working?" from the real Wave 0 analytics endpoints.
*
* Mirrors StatsStrip's idiom exactly: label + mono value + tone + delta.
* Honest zero/pending states — the PRIMITIVES cell shows "—" / "metric
* pending" (like StatsStrip's CACHE-HIT) rather than fabricate a health
* signal; per-primitive health (US-006) lands in Wave 3.
*/

import type { SparklineTone } from './sparkline'
import {
useActivationMetrics,
useMissionSuccessRate,
useErrorsBySubsystem,
useWidgetEngagement,
} from '@/hooks/use-analytics-api'

interface Cell {
label: string
value: string
tone: SparklineTone
delta: string
}

export function IsItWorkingStrip() {
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 worst = errors?.by_subsystem?.length
? [...errors.by_subsystem].sort((a, b) => b.count - a.count)[0]
: null

const sessions = widget?.sessions ?? 0
const widgetEvents = widget?.by_event_type?.reduce((sum, ev) => sum + ev.count, 0) ?? 0

const cells: Cell[] = [
{
label: 'ACTIVATION',
value: totalWs > 0 ? `${activationPct}%` : '—',
tone: totalWs > 0 ? 'ok' : 'muted',
delta: totalWs > 0 ? `${activation?.activated ?? 0}/${totalWs} workspaces` : 'no workspaces',
},
{
label: 'MISSIONS',
value: missionTotal > 0 ? `${missionPct}%` : '—',
tone: missionTotal > 0 ? 'ok' : 'muted',
delta: missionTotal > 0
? `${mission?.successful_executions ?? 0}/${missionTotal} ok`
: 'no missions yet',
},
{
label: 'ERRORS',
value: String(errorTotal),
tone: errorTotal > 0 ? 'err' : 'ok',
delta: errorTotal > 0 && worst ? `${worst.subsystem} (${worst.count}) · 24h` : 'none · 24h',
},
{
label: 'WIDGET',
value: String(sessions),
tone: sessions > 0 ? 'info' : 'muted',
delta: sessions > 0 ? `${widgetEvents} events · 7d` : '— · 7d',
},
{
label: 'PRIMITIVES',
value: '—',
tone: 'muted',
delta: 'metric pending',
Comment on lines +30 to +80
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 | 🟠 Major | ⚡ Quick win

Don't collapse failed analytics calls into healthy tiles.

These hooks only read data, so loading/error states fall through to the same copy as real zero-data. The worst case is the ERRORS tile: a failed request renders 0 with ok, which is a false green on an “is it working?” strip. Render an explicit pending/unavailable state before using the current empty-state copy.

🐛 Sketch of the fix
-export function IsItWorkingStrip() {
-  const { data: activation } = useActivationMetrics()
-  const { data: mission } = useMissionSuccessRate()
-  const { data: errors } = useErrorsBySubsystem('24h')
-  const { data: widget } = useWidgetEngagement('7d')
+export function IsItWorkingStrip() {
+  const activationQuery = useActivationMetrics()
+  const missionQuery = useMissionSuccessRate()
+  const errorsQuery = useErrorsBySubsystem('24h')
+  const widgetQuery = useWidgetEngagement('7d')
+
+  const { data: activation } = activationQuery
+  const { data: mission } = missionQuery
+  const { data: errors } = errorsQuery
+  const { data: widget } = widgetQuery
+  const metricsUnavailable = [activationQuery, missionQuery, errorsQuery, widgetQuery].some(
+    (query) => query.isError,
+  )

Then switch each cell to neutral copy such as '—' / 'metrics unavailable' (or 'loading…') when a query is not ready, instead of 0, none · 24h, and no workspaces.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/components/command-center/is-it-working-strip.tsx` around lines 30 -
80, The tiles collapse failed or loading analytics into healthy zero-state
because the component only reads data from useActivationMetrics,
useMissionSuccessRate, useErrorsBySubsystem, and useWidgetEngagement; update the
component to check each hook's readiness (isLoading/isError/isFetching/ready
flags) before deriving totals/percentages and building the cells array, and when
a query is not ready render neutral placeholders like '—' for value and 'metrics
unavailable' or 'loading…' for delta (instead of 0/'none · 24h'/'no
workspaces'), specifically change the logic that computes totalWs/activationPct,
missionTotal/missionPct, errorTotal/worst, sessions/widgetEvents and the
corresponding delta/tone assignments in the cells definition so failed requests
show a neutral tone and copy rather than green/ok or 0.

},
]

return (
<div className="cc-stats">
{cells.map((c) => (
<div key={c.label} className="cell">
<div className="l">
<Dot tone={c.tone} />
{c.label}
</div>
<div className={`v ${c.tone === 'muted' ? '' : c.tone}`}>{c.value}</div>
<div style={{ display: 'flex', gap: 8, alignItems: 'flex-end' }}>
<span className="delta" style={{ flex: 1, minWidth: 0 }}>
{c.delta}
</span>
</div>
</div>
))}
</div>
)
}

function Dot({ tone }: { tone: SparklineTone }) {
if (tone === 'muted') return null
const colors: Record<SparklineTone, string> = {
ok: 'hsl(82 50% 22%)',
err: 'hsl(var(--accent))',
warn: 'hsl(38 78% 27%)',
info: 'hsl(var(--info))',
muted: 'transparent',
}
return (
<span
style={{
width: 6,
height: 6,
borderRadius: '50%',
background: colors[tone],
display: 'inline-block',
}}
/>
)
}
Loading
Loading