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
17 changes: 9 additions & 8 deletions components/pandora/AdaptiveProfileCard.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { MoreHorizontal, RefreshCw } from "lucide-react";
import { profileSnapshot } from "./mock-data";
import type { ProfileSnapshot } from "./types";

function ConfidenceRing({ value, label }: { value: number; label: string }) {
const safeValue = Math.max(0, Math.min(100, Number.isFinite(value) ? value : 0));
const radius = 42;
const circumference = 2 * Math.PI * radius;
const offset = circumference * (1 - value / 100);
const offset = circumference * (1 - safeValue / 100);

return (
<div className="pd-ring">
Expand All @@ -17,19 +18,19 @@ function ConfidenceRing({ value, label }: { value: number; label: string }) {
);
}

export function AdaptiveProfileCard({ loading }: { loading: boolean }) {
export function AdaptiveProfileCard({ profile, loading = false }: { profile: ProfileSnapshot; loading?: boolean }) {
return (
<section className="pd-card">
<div className="pd-section-head">
<div><p className="pd-label">Adaptive Profile</p><h3>{profileSnapshot.name}</h3></div>
<span className="pd-pill pd-pill-slate">{profileSnapshot.status}</span>
<div><p className="pd-label">Adaptive Profile</p><h3>{profile.name}</h3></div>
<span className="pd-pill pd-pill-slate">{profile.status}</span>
</div>
{loading ? <div className="pd-loading" aria-label="Loading adaptive profile shell" /> : <>
<div className="pd-profile-main">
<ConfidenceRing value={profileSnapshot.confidencePercent} label={profileSnapshot.confidenceLabel} />
<div><strong>{profileSnapshot.summary}</strong><p>{profileSnapshot.lastRefreshed}</p></div>
<ConfidenceRing value={profile.confidencePercent} label={profile.confidenceLabel} />
<div><strong>{profile.summary}</strong><p>{profile.lastRefreshed}</p></div>
</div>
<div className="pd-traits">{profileSnapshot.traits.map((trait) => <span key={trait}>{trait}</span>)}</div>
<div className="pd-traits">{profile.traits.map((trait) => <span key={trait}>{trait}</span>)}</div>
<div className="pd-card-row">
<button type="button" className="pd-secondary-btn" disabled><RefreshCw size={16} aria-hidden="true" />Refresh Profile</button>
<button type="button" className="pd-icon-button" aria-label="More profile options" disabled><MoreHorizontal size={18} aria-hidden="true" /></button>
Expand Down
15 changes: 7 additions & 8 deletions components/pandora/AskPandoraHero.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
import { profileSnapshot } from "./mock-data";

export function AskPandoraHero() {
export function AskPandoraHero({ hero, evidence, warnings }: { hero: { title: string; description: string; primaryAction: string; secondaryAction: string }; evidence: string; warnings: string[] }) {
return (
<section className="pd-hero">
<div>
<p className="pd-label">Ask Pandora</p>
<h2>Pandora dashboard is an authenticated mock shell.</h2>
<p>This route is for layout review only. It does not present live memory health, retrieval scores, profile state, or queue activity until those claims are backed by implemented routes, database policy, and tests.</p>
<h2>{hero.title}</h2>
<p>{hero.description}</p>
</div>
<div className="pd-hero-actions">
<button type="button" className="pd-primary-btn" disabled>Context pack backend pending</button>
<button type="button" className="pd-secondary-btn" disabled>Retrieval eval backend pending</button>
<button type="button" className="pd-primary-btn" disabled>{hero.primaryAction}</button>
<button type="button" className="pd-secondary-btn" disabled>{hero.secondaryAction}</button>
</div>
<div className="pd-evidence">{profileSnapshot.evidence}</div>
<div className="pd-evidence">{evidence}</div>
{warnings.length ? <div className="pd-envelope"><strong>Loader warnings</strong><p>{warnings.join(" ")}</p></div> : null}
</section>
);
}
7 changes: 3 additions & 4 deletions components/pandora/DiagnosticsCard.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { coreSystems, gatedSystems } from "./mock-data";
import type { SystemRow } from "./types";
import type { PandoraDashboardData, SystemRow } from "./types";

function SystemStatusRow({ row }: { row: SystemRow }) { return <div className="pd-system-row"><span>{row.label}</span><strong className={`pd-state-${row.state}`}>{row.value}</strong></div>; }

export function DiagnosticsCard({ loading }: { loading: boolean }) {
return <section className="pd-card"><div className="pd-section-head"><div><p className="pd-label">Diagnostics</p><h3>Authenticated mock shell only.</h3></div></div>{loading ? <div className="pd-loading" aria-label="Loading diagnostics" /> : <><div className="pd-system-list">{coreSystems.map((row) => <SystemStatusRow row={row} key={row.label} />)}</div><div className="pd-system-list pd-gated-list">{gatedSystems.map((row) => <SystemStatusRow row={row} key={row.label} />)}</div><div className="pd-envelope"><strong>Action Envelope</strong><p>Pending backend proof. Do not treat this UI as live engine evidence.</p></div><button type="button" className="pd-secondary-btn" disabled title="Backend wiring pending">Run Smoke Test</button></>}</section>;
export function DiagnosticsCard({ diagnostics, loading = false }: { diagnostics: PandoraDashboardData["diagnostics"]; loading?: boolean }) {
return <section className="pd-card"><div className="pd-section-head"><div><p className="pd-label">Diagnostics</p><h3>Live RLS Data</h3></div></div>{loading ? <div className="pd-loading" aria-label="Loading diagnostics" /> : <><div className="pd-system-list">{diagnostics.coreSystems.map((row) => <SystemStatusRow row={row} key={row.label} />)}</div><div className="pd-system-list pd-gated-list">{diagnostics.gatedSystems.map((row) => <SystemStatusRow row={row} key={row.label} />)}</div><div className="pd-envelope"><strong>{diagnostics.envelope.title}</strong><p>{diagnostics.envelope.description}</p></div><button type="button" className="pd-secondary-btn" disabled title="Smoke tests run outside the dashboard">Run Smoke Test</button></>}</section>;
}
6 changes: 3 additions & 3 deletions components/pandora/MemorySpacesCard.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { LockKeyhole } from "lucide-react";
import { memorySpaces } from "./mock-data";
import type { MemorySpace } from "./types";
import { COLORS, cn } from "./theme";

export function MemorySpacesCard() {
return <section className="pd-card"><div className="pd-section-head"><div><p className="pd-label">Memory Spaces</p><h3>Namespace separation enforced</h3></div><LockKeyhole size={20} aria-hidden="true" /></div><div className="pd-space-grid">{memorySpaces.map((space) => { const color = COLORS[space.color]; return <button type="button" className={cn("pd-space", color.border)} key={space.id} aria-label={`Open ${space.label} namespace shell`}><div className="pd-card-row"><span className={cn("pd-dot", color.dot)} /><span className="pd-pill">{space.status}</span></div><strong>{space.label}</strong><small>{space.type}</small><p>{space.description}</p><div className="pd-space-metrics"><span>{space.memories.toLocaleString()}<small>memories</small></span><span>{space.people}<small>people</small></span><span>{space.projects}<small>projects</small></span></div></button>; })}</div></section>;
export function MemorySpacesCard({ spaces }: { spaces: MemorySpace[] }) {
return <section className="pd-card"><div className="pd-section-head"><div><p className="pd-label">Memory Spaces</p><h3>Namespace separation enforced</h3></div><LockKeyhole size={20} aria-hidden="true" /></div><div className="pd-space-grid">{spaces.map((space) => { const color = COLORS[space.color]; return <button type="button" className={cn("pd-space", color.border)} key={space.id} aria-label={`${space.label} namespace live summary`} disabled><div className="pd-card-row"><span className={cn("pd-dot", color.dot)} /><span className="pd-pill">{space.status}</span></div><strong>{space.label}</strong><small>{space.type}</small><p>{space.description}</p><div className="pd-space-metrics"><span>{space.memories.toLocaleString()}<small>memories</small></span><span>{space.people}<small>people</small></span><span>{space.projects}<small>projects</small></span></div></button>; })}</div></section>;
}
42 changes: 24 additions & 18 deletions components/pandora/PandoraDashboard.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,24 @@
"use client";

import { Package } from "lucide-react";
import { Archive, ClipboardList, GitBranch, History, Package, ShieldCheck } from "lucide-react";
import { AdaptiveProfileCard } from "./AdaptiveProfileCard";
import { AskPandoraHero } from "./AskPandoraHero";
import { DiagnosticsCard } from "./DiagnosticsCard";
import { MemorySpacesCard } from "./MemorySpacesCard";
import { MobileBottomNav } from "./MobileBottomNav";
import { RecentEventsTimeline } from "./RecentEventsTimeline";
import { Sidebar } from "./Sidebar";
import { StatCard } from "./StatCard";
import { TopBar } from "./TopBar";
import { WorkQueueCard } from "./WorkQueueCard";
import type { PandoraDashboardData, StatItem } from "./types";
import { useState } from "react";

const statIcons = [History, Package, ShieldCheck, GitBranch, Archive, ClipboardList];

export function PandoraDashboard({ dashboardData }: { dashboardData: PandoraDashboardData }) {
const [activeNav, setActiveNav] = useState("Dashboard");
const stats: StatItem[] = dashboardData.stats.map((stat) => ({ ...stat, icon: Package }));
const stats: StatItem[] = dashboardData.stats.map((stat, index) => ({ ...stat, icon: statIcons[index] ?? Package }));

return (
<div className="pd-shell">
Expand All @@ -29,26 +37,24 @@ export function PandoraDashboard({ dashboardData }: { dashboardData: PandoraDash
<span className="pd-pill pd-pill-slate">Semantic Gated</span>
</div>
</div>
<section className="pd-hero">
<div>
<p className="pd-label">Live Truth Boundary</p>
<h2>{dashboardData.hero.title}</h2>
<p>{dashboardData.hero.description}</p>
</div>
<div className="pd-evidence">{dashboardData.evidence}</div>
</section>
<AskPandoraHero hero={dashboardData.hero} evidence={dashboardData.evidence} warnings={dashboardData.warnings} />
<section className="pd-stat-grid" aria-label="Pandora status stats">
{stats.map((stat) => <StatCard stat={stat} key={stat.id} />)}
</section>
<section className="pd-card">
<div className="pd-section-head"><div><p className="pd-label">Live Snapshot</p><h3>Server scoped state</h3></div></div>
<div className="pd-mini-grid">
<div className="pd-mini"><strong>{dashboardData.memorySpaces.length}</strong><span>namespaces</span></div>
<div className="pd-mini"><strong>{dashboardData.workQueue.openLoops}</strong><span>open loops</span></div>
<div className="pd-mini"><strong>{dashboardData.workQueue.needsReview}</strong><span>needs review</span></div>
<div className="pd-dashboard-grid">
<div className="pd-dashboard-col pd-dashboard-col-wide">
<MemorySpacesCard spaces={dashboardData.memorySpaces} />
<RecentEventsTimeline events={dashboardData.timelineEvents} />

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Sort combined events before showing the timeline

With both namespaces populated, the loader builds timelineEvents by flattening namespace reads in fixed order (real_life before au) and then taking the first six. Rendering that array here makes the “Recent Memory Events” card omit newer AU rows whenever six older real-life rows exist, so the live dashboard can show an inaccurate recent timeline; sort the combined events by created_at before slicing.

Useful? React with 👍 / 👎.

</div>
<div className="pd-envelope"><strong>{dashboardData.diagnostics.envelope.title}</strong><p>{dashboardData.diagnostics.envelope.description}</p></div>
</section>
<div className="pd-dashboard-col">
<WorkQueueCard queue={dashboardData.workQueue} />
<AdaptiveProfileCard profile={dashboardData.profileSnapshot} />
</div>
<div className="pd-dashboard-col">
<DiagnosticsCard diagnostics={dashboardData.diagnostics} />
<section className="pd-card"><div className="pd-section-head"><div><p className="pd-label">Operator Boundary</p><h3>{dashboardData.operatorLabel}</h3></div></div><div className="pd-mini-grid"><div className="pd-mini"><strong>{dashboardData.memorySpaces.length}</strong><span>namespaces</span></div><div className="pd-mini"><strong>{dashboardData.warnings.length}</strong><span>warnings</span></div><div className="pd-mini"><strong>{dashboardData.live ? "Live" : "No live data"}</strong><span>loader</span></div></div></section>
</div>
</div>
</main>
</div>
<MobileBottomNav activeNav={activeNav} onNavChange={setActiveNav} />
Expand Down
7 changes: 4 additions & 3 deletions components/pandora/RecentEventsTimeline.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { timelineEvents } from "./mock-data";
import { BadgeCheck } from "lucide-react";
import type { TimelineEventData } from "./types";
import { COLORS, cn } from "./theme";

export function RecentEventsTimeline() {
return <section className="pd-card pd-timeline-card"><div className="pd-section-head"><div><p className="pd-label">Recent Memory Events</p><h3>Checkpoint timeline</h3></div></div><ol className="pd-timeline">{timelineEvents.map((event) => { const Icon = event.icon; const color = COLORS[event.color]; return <li key={event.id}><div className={cn("pd-timeline-icon", color.iconBg, color.iconText)}><Icon size={16} aria-hidden="true" /></div><div><div className="pd-event-title"><strong>{event.title}</strong><time>{event.time}</time></div><p>{event.desc}</p></div></li>; })}</ol></section>;
export function RecentEventsTimeline({ events }: { events: TimelineEventData[] }) {
return <section className="pd-card pd-timeline-card"><div className="pd-section-head"><div><p className="pd-label">Recent Memory Events</p><h3>Checkpoint timeline</h3></div></div><ol className="pd-timeline">{events.length ? events.map((event) => { const color = COLORS[event.color]; return <li key={event.id}><div className={cn("pd-timeline-icon", color.iconBg, color.iconText)}><BadgeCheck size={16} aria-hidden="true" /></div><div><div className="pd-event-title"><strong>{event.title}</strong><time>{event.time}</time></div><p>{event.desc}</p></div></li>; }) : <li><div className="pd-timeline-icon pd-icon-bg-slate pd-icon-text-slate"><BadgeCheck size={16} aria-hidden="true" /></div><div><div className="pd-event-title"><strong>No live events returned</strong><time>Empty state</time></div><p>No timeline rows are fabricated.</p></div></li>}</ol></section>;
}
10 changes: 5 additions & 5 deletions components/pandora/WorkQueueCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@ function MiniMetric({ label, value }: { label: string; value: number }) { return
export function WorkQueueCard({ queue }: { queue: WorkQueueData }) {
const total = Object.values(queue).reduce((sum, value) => sum + value, 0);
const items = [
{ label: "Open Loops", value: queue.openLoops, desc: "Backend pending", icon: AlertCircle },
{ label: "Needs Review", value: queue.needsReview, desc: "Backend pending", icon: ListChecks },
{ label: "Pack Supersession", value: queue.packSupersessionNeeded, desc: "Backend pending", icon: GitMerge },
{ label: "People Map Design", value: queue.peopleMapDesignNeeded, desc: "Backend pending", icon: UserRoundSearch },
{ label: "Open Loops", value: queue.openLoops, desc: "Live open-loop rows needing operator follow-up", icon: AlertCircle },
{ label: "Needs Review", value: queue.needsReview, desc: "Live capture/review rows awaiting review", icon: ListChecks },
{ label: "Pack Supersession", value: queue.packSupersessionNeeded, desc: "Duplicate active master packs requiring attention", icon: GitMerge },
{ label: "People Map Design", value: queue.peopleMapDesignNeeded, desc: "No fabricated people-map work is created", icon: UserRoundSearch },
];
return <section className="pd-card"><div className="pd-section-head"><div><p className="pd-label">Work Queue</p><h3>{total} actionable items</h3></div><span className="pd-pill pd-pill-slate">Mock only</span></div><div className="pd-queue-list">{items.map((item) => { const Icon = item.icon; return <div className="pd-queue-item" key={item.label}><Icon size={18} aria-hidden="true" /><div><strong>{item.label}</strong><p>{item.desc}</p></div><b>{item.value}</b></div>; })}</div><div className="pd-mini-grid"><MiniMetric label="Stale Packs" value={queue.stalePacks} /><MiniMetric label="Refresh Due" value={queue.profileRefreshDue} /><MiniMetric label="Failed Tests" value={queue.failedTests} /></div></section>;
return <section className="pd-card"><div className="pd-section-head"><div><p className="pd-label">Work Queue</p><h3>{total} actionable items</h3></div><span className="pd-pill pd-pill-emerald">Live RLS Data</span></div><div className="pd-queue-list">{items.map((item) => { const Icon = item.icon; return <div className="pd-queue-item" key={item.label}><Icon size={18} aria-hidden="true" /><div><strong>{item.label}</strong><p>{item.desc}</p></div><b>{item.value}</b></div>; })}</div><div className="pd-mini-grid"><MiniMetric label="Pruning Review" value={queue.stalePacks} /><MiniMetric label="Profile Missing" value={queue.profileRefreshDue} /><MiniMetric label="Failed Tests" value={queue.failedTests} /></div></section>;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Mark partial queue reads as degraded

When a queue backing table such as memory_capture_candidates, memory_review_queue_items, or memory_pruning_candidates is unreadable, loadPandoraDashboardData converts that read to [] with a warning and live=false; this card only receives the zeroed counts and still labels the result Live RLS Data, so an unavailable table can be presented as a trustworthy 0 actionable items state. Pass the loader status/warnings into the card or use a partial/degraded label for these counts.

Useful? React with 👍 / 👎.

}
29 changes: 10 additions & 19 deletions components/pandora/mock-data.ts
Original file line number Diff line number Diff line change
@@ -1,30 +1,21 @@
import { BadgeCheck, Package } from "lucide-react";
import type { MemorySpace, StatItem, SystemRow, WorkQueueData } from "./types";
import { Package } from "lucide-react";
import type { MemorySpace, PandoraDashboardData, StatItem, SystemRow, TimelineEventData, WorkQueueData } from "./types";

export const profileSnapshot = { name: "Fixture", status: "Fixture", confidencePercent: 0, confidenceLabel: "N/A", summary: "Fixture", lastRefreshed: "Fixture", traits: ["Fixture"], evidence: "Fixture" } as const;
export const profileSnapshot = { name: "Fixture", status: "Mock only", confidencePercent: 0, confidenceLabel: "No live data", summary: "No live data fixture profile.", lastRefreshed: "No live data", traits: ["Fixture", "Mock only"], evidence: "No live data" };

export const mockStats: StatItem[] = [
{ id: "one", title: "One", value: "0", subtitle: "Fixture", icon: Package, color: "slate", sparklineData: [0] },
{ id: "fixture", title: "Fixture", value: "No live data", subtitle: "Mock only", icon: Package, color: "slate", sparklineData: [0, 0] },
];

export const memorySpaces: MemorySpace[] = [
{ id: "real_life", label: "real_life", type: "Primary", description: "Fixture", memories: 0, people: 0, projects: 0, status: "Degraded", color: "emerald" },
{ id: "au", label: "au", type: "Secondary", description: "Fixture", memories: 0, people: 0, projects: 0, status: "Degraded", color: "purple" },
{ id: "real_life", label: "real_life", type: "Fixture", description: "Mock only: no live data.", memories: 0, people: 0, projects: 0, status: "Degraded", color: "emerald" },
{ id: "au", label: "au", type: "Fixture", description: "Mock only: no live data.", memories: 0, people: 0, projects: 0, status: "Degraded", color: "purple" },
];

export const workQueue: WorkQueueData = { needsReview: 0, openLoops: 0, stalePacks: 0, failedTests: 0, profileRefreshDue: 0, packSupersessionNeeded: 0, peopleMapDesignNeeded: 0 };

export const timelineEvents = [
{ id: "one", icon: BadgeCheck, color: "slate", title: "One", time: "Now", desc: "Fixture" },
] as const;

export const coreSystems: SystemRow[] = [
{ label: "One", value: "OK", state: "healthy" },
];

export const gatedSystems: SystemRow[] = [
{ label: "Two", value: "OK", state: "gated" },
];

export const timelineEvents: TimelineEventData[] = [{ id: "fixture", color: "slate", title: "Fixture", time: "No live data", desc: "Mock only", namespace: "real_life" }];
export const coreSystems: SystemRow[] = [{ label: "Fixture", value: "No live data", state: "idle" }];
export const gatedSystems: SystemRow[] = [{ label: "Semantic retrieval", value: "Gated", state: "gated" }];
export const fixtureDashboardData: PandoraDashboardData = { generatedAt: "No live data", operatorLabel: "Fixture", live: false, warnings: ["Mock only"], hero: { title: "Fixture dashboard", description: "Mock only: no live data.", primaryAction: "No live data", secondaryAction: "Semantic gated" }, evidence: "No live data", stats: [{ id: "fixture", title: "Fixture", value: "No live data", subtitle: "Mock only", color: "slate", sparklineData: [0, 0] }], memorySpaces, workQueue, profileSnapshot, timelineEvents, diagnostics: { coreSystems, gatedSystems, envelope: { title: "Fixture", description: "Mock only: no live data." } } };
export const navItems = ["Dashboard", "Memory Feed", "Context Packs", "Adaptive Profiles", "Open Loops", "People", "Projects", "Retrieval Tests", "Settings"] as const;
export const mobileNavItems = ["Dashboard", "Memory Feed", "Queue", "Profiles", "More"];
Loading
Loading