-
Notifications
You must be signed in to change notification settings - Fork 1
Add Pandora dashboard UI shell #126
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| import { PandoraDashboard } from "@/components/pandora/PandoraDashboard"; | ||
|
|
||
| export default function PandoraPage() { | ||
| return <PandoraDashboard />; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
In deployments without an external auth wall, requesting Useful? React with 👍 / 👎. |
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| import { MoreHorizontal, RefreshCw } from "lucide-react"; | ||
|
|
||
| function ConfidenceRing({ value }: { value: number }) { const radius = 42; const circumference = 2 * Math.PI * radius; const offset = circumference * (1 - value / 100); return <div className="pd-ring"><svg viewBox="0 0 100 100" aria-label={`${value}% confidence`}><circle cx="50" cy="50" r={radius} className="pd-ring-bg" /><circle cx="50" cy="50" r={radius} className="pd-ring-fg" strokeDasharray={circumference} strokeDashoffset={offset} /></svg><strong>{value}%</strong></div>; } | ||
|
|
||
| export function AdaptiveProfileCard({ loading }: { loading: boolean }) { | ||
| return <section className="pd-card"><div className="pd-section-head"><div><p className="pd-label">Adaptive Profile</p><h3>Writer v2</h3></div><span className="pd-pill pd-pill-emerald">Active</span></div>{loading ? <div className="pd-loading" aria-label="Loading adaptive profile" /> : <><div className="pd-profile-main"><ConfidenceRing value={88} /><div><strong>11 memories • 6 preferences • 2 facts</strong><p>Last refreshed 1h ago</p></div></div><div className="pd-traits">{["Analytical", "Methodical", "Private", "Detail-oriented"].map((trait) => <span key={trait}>{trait}</span>)}</div><div className="pd-card-row"><button type="button" className="pd-secondary-btn"><RefreshCw size={16} aria-hidden="true" />Refresh Profile</button><button type="button" className="pd-icon-button" aria-label="More profile options"><MoreHorizontal size={18} aria-hidden="true" /></button></div></>}</section>; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| export function AskPandoraHero() { | ||
| return ( | ||
| <section className="pd-hero"> | ||
| <div><p className="pd-label">Ask Pandora</p><h2>Pandora is stable. The next risk is memory clutter, not stream failure.</h2><p>V0.4 is hardened: payload caps, profile refresh reruns, namespace isolation, and action envelopes are working. The next cleanup target is retrieval evals plus master-pack supersession.</p></div> | ||
| <div className="pd-hero-actions"><button type="button" className="pd-primary-btn">Refresh Context Pack</button><button type="button" className="pd-secondary-btn">Run Retrieval Eval</button></div> | ||
| <div className="pd-evidence">Based on 11 memories • 6 preferences • 2 facts • Confidence 0.88 • request_id enabled</div> | ||
| </section> | ||
| ); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| import { coreSystems, gatedSystems } from "./mock-data"; | ||
| import type { 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>Core ungated systems operational.</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>MCP Envelope</strong><p>Responses now expose ok, request_id, fallback_used, and capped payloads.</p></div><button type="button" className="pd-secondary-btn" disabled title="Backend wiring pending">Run Smoke Test</button></>}</section>; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| import { LockKeyhole } from "lucide-react"; | ||
| import { memorySpaces } from "./mock-data"; | ||
| 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>; | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,4 @@ | ||||||||||
| import { Brain, Gauge, History, ListChecks, MoreHorizontal } from "lucide-react"; | ||||||||||
| import { mobileNavItems } from "./mock-data"; | ||||||||||
| const icons = [Gauge, History, ListChecks, Brain, MoreHorizontal]; | ||||||||||
| export function MobileBottomNav({ activeNav, onNavChange }: { activeNav: string; onNavChange: (item: string) => void }) { return <nav className="pd-mobile-nav" aria-label="Mobile Pandora navigation">{mobileNavItems.map((item, index) => { const Icon = icons[index]; const active = item === activeNav || (item === "Dashboard" && activeNav === "Dashboard"); return <button type="button" key={item} aria-current={active ? "page" : undefined} onClick={() => onNavChange(item === "Feed" ? "Memory Feed" : item)}><span><Icon size={18} aria-hidden="true" />{item === "Queue" ? <i aria-label="Queue has attention items" /> : null}</span>{item}</button>; })}</nav>; } | ||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
♿ Proposed fix-{item === "Queue" ? <i aria-label="Queue has attention items" /> : null}
+{item === "Queue" ? <span className="pd-badge-dot" role="status" aria-label="Queue has attention items" /> : null}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents🎯 Functional Correctness | 🟠 Major | ⚡ Quick win Redundant The clause 🐛 Proposed fix-const active = item === activeNav || (item === "Dashboard" && activeNav === "Dashboard");
+const active = item === "Feed" ? activeNav === "Memory Feed" : item === activeNav;📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,43 @@ | ||
| "use client"; | ||
|
|
||
| import { useEffect, useState } from "react"; | ||
| import { AskPandoraHero } from "./AskPandoraHero"; | ||
| import { AdaptiveProfileCard } from "./AdaptiveProfileCard"; | ||
| import { DiagnosticsCard } from "./DiagnosticsCard"; | ||
| import { MemorySpacesCard } from "./MemorySpacesCard"; | ||
| import { MobileBottomNav } from "./MobileBottomNav"; | ||
| import { mockStats, workQueue } from "./mock-data"; | ||
| import { RecentEventsTimeline } from "./RecentEventsTimeline"; | ||
| import { Sidebar } from "./Sidebar"; | ||
| import { StatCard } from "./StatCard"; | ||
| import { TopBar } from "./TopBar"; | ||
| import { WorkQueueCard } from "./WorkQueueCard"; | ||
|
|
||
| export function PandoraDashboard() { | ||
| const [activeNav, setActiveNav] = useState("Dashboard"); | ||
| const [isSimulatingLoad, setIsSimulatingLoad] = useState(true); | ||
|
|
||
| useEffect(() => { | ||
| const timer = window.setTimeout(() => setIsSimulatingLoad(false), 900); | ||
| return () => window.clearTimeout(timer); | ||
| }, []); | ||
|
|
||
| return ( | ||
| <div className="pd-shell"> | ||
| <Sidebar activeNav={activeNav} onNavChange={setActiveNav} /> | ||
| <div className="pd-main"> | ||
| <TopBar /> | ||
| <main className="pd-content"> | ||
| <div className="pd-header-row"> | ||
| <div><p className="pd-label">Pandora Dashboard</p><h1>Memory Command Center</h1><p>Stable internal engine. Not productized. Ready for evals and pack supersession.</p></div> | ||
| <div className="pd-badges"><span className="pd-pill pd-pill-emerald">V0.4 Stable</span><span className="pd-pill pd-pill-purple">Envelope Live</span><span className="pd-pill pd-pill-slate">Semantic Gated</span></div> | ||
| </div> | ||
| <AskPandoraHero /> | ||
| <section className="pd-stat-grid" aria-label="Pandora status stats">{mockStats.map((stat) => <StatCard stat={stat} key={stat.id} />)}</section> | ||
| <section className="pd-dashboard-grid"><div className="pd-column"><WorkQueueCard queue={workQueue} /><MemorySpacesCard /></div><RecentEventsTimeline /><div className="pd-column"><AdaptiveProfileCard loading={isSimulatingLoad} /><DiagnosticsCard loading={isSimulatingLoad} /></div></section> | ||
| </main> | ||
| </div> | ||
| <MobileBottomNav activeNav={activeNav} onNavChange={setActiveNav} /> | ||
| </div> | ||
| ); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Fixed
padding-bottomdoesn't account for device safe-area inset..pd-mobile-navheight grows withcalc(env(safe-area-inset-bottom) + .75rem), but.pd-shell's reservedpadding-bottom: 86pxis a fixed value. On devices with a larger safe-area inset (e.g. iPhones with home indicator), the nav bar can be taller than 86px, causing page content to be obscured behind the fixed bottom nav.🩹 Proposed fix
📝 Committable suggestion
🤖 Prompt for AI Agents