Add Pandora dashboard UI shell - #126
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
📝 WalkthroughWalkthroughThis PR introduces a new Pandora dashboard feature: a ChangesPandora Dashboard Feature
Estimated code review effort: 3 (Moderate) | ~25 minutes Sequence Diagram(s)sequenceDiagram
participant PandoraPage
participant PandoraDashboard
participant AdaptiveProfileCard
participant DiagnosticsCard
PandoraPage->>PandoraDashboard: render()
PandoraDashboard->>PandoraDashboard: useEffect starts 900ms timeout
PandoraDashboard->>AdaptiveProfileCard: render(loading=true)
PandoraDashboard->>DiagnosticsCard: render(loading=true)
PandoraDashboard-->>PandoraDashboard: timeout fires, isSimulatingLoad=false
PandoraDashboard->>AdaptiveProfileCard: re-render(loading=false)
PandoraDashboard->>DiagnosticsCard: re-render(loading=false)
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: f68d69c453
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| import { PandoraDashboard } from "@/components/pandora/PandoraDashboard"; | ||
|
|
||
| export default function PandoraPage() { | ||
| return <PandoraDashboard />; |
There was a problem hiding this comment.
Gate the Pandora dashboard route
In deployments without an external auth wall, requesting /pandora renders PandoraDashboard immediately because this page does not check the Supabase session and the repo middleware only refreshes auth cookies rather than blocking routes. Since the dashboard shows internal memory namespaces, counts, and status labels, keep this behind the same authenticated/admin boundary as the memory browser or replace it with unauthenticated-safe foundation copy.
Useful? React with 👍 / 👎.
|
|
||
| export const mockStats: StatItem[] = [ | ||
| { id: "health", title: "Memory Health", value: "Stable", subtitle: "Ungated systems healthy", icon: ShieldCheck, color: "emerald", sparklineData: [68, 70, 72, 74, 74, 76, 78] }, | ||
| { id: "retrieval", title: "Retrieval Accuracy", value: "94.3%", subtitle: "vs last 7 days", trend: "↑ 2.4%", icon: Target, color: "indigo", sparklineData: [82, 84, 83, 88, 90, 92, 94] }, |
There was a problem hiding this comment.
Replace unsupported retrieval metric with a gated state
When /pandora renders the stat grid, this row presents “Retrieval Accuracy 94.3%” as if semantic retrieval has live eval results, while the project safety rules require retrieval not be claimed as enabled without proof and this same mock dataset later labels semantic retrieval as “Gated Off.” This can mislead operators into treating retrieval as shipped; make the card explicitly gated/planned or remove the accuracy number until it is backed by route/test evidence.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (7)
components/pandora/mock-data.ts (1)
41-42: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueConsider typing
navItems/mobileNavItemsas readonly literal tuples.Declaring these
as constwould give a literal union type (e.g.,"Dashboard" | "Feed" | ...) that downstreamactiveNav/onNavChangestate could reuse for compile-time safety, instead of plainstring[].♻️ Proposed refactor
-export const navItems = ["Dashboard", "Memory Feed", "Context Packs", "Adaptive Profiles", "Open Loops", "People", "Projects", "Retrieval Tests", "Settings"]; -export const mobileNavItems = ["Dashboard", "Feed", "Queue", "Profiles", "More"]; +export const navItems = ["Dashboard", "Memory Feed", "Context Packs", "Adaptive Profiles", "Open Loops", "People", "Projects", "Retrieval Tests", "Settings"] as const; +export const mobileNavItems = ["Dashboard", "Feed", "Queue", "Profiles", "More"] as const;🤖 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 `@components/pandora/mock-data.ts` around lines 41 - 42, The nav item arrays are currently inferred as mutable string arrays, which loses the literal union type needed by downstream activeNav/onNavChange state. Update the navItems and mobileNavItems declarations in mock-data.ts to readonly literal tuples using const assertion so their item names can be reused as compile-time-safe unions. Keep the existing values the same, but make the exported symbols infer literal types instead of string[].components/pandora/types.ts (1)
16-26: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueHardcoded
idunion tightly couples the type to current mock data.
MemorySpace.idis restricted to"real_life" | "au", meaning adding a third memory space later requires editing this shared type. Since this is mock/demo data intended to grow, considerstring(or a separate exported union kept in sync intentionally) if new spaces are expected soon.🤖 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 `@components/pandora/types.ts` around lines 16 - 26, The MemorySpace type currently hardcodes id to only "real_life" | "au", which makes the shared mock data shape hard to extend. Update MemorySpace.id in types.ts to be a more flexible identifier such as string, or move the allowed values into a separately exported union that can be intentionally maintained as new spaces are added. Keep the change scoped to the MemorySpace definition so existing fields and consumers remain compatible.components/pandora/PandoraDashboard.tsx (1)
18-23: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueConsider announcing loading completion to assistive tech.
isSimulatingLoadtoggles theAdaptiveProfileCard/DiagnosticsCardcontent in place without anyaria-liveregion, so screen reader users get no notification when the placeholder is replaced by real content.Also applies to: 37-37
🤖 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 `@components/pandora/PandoraDashboard.tsx` around lines 18 - 23, The loading state in PandoraDashboard is swapped in place without any assistive announcement, so screen readers may miss when the placeholders become real content. Update the PandoraDashboard component around isSimulatingLoad/useEffect and the AdaptiveProfileCard/DiagnosticsCard render path to add an aria-live region (or equivalent accessible status announcement) that signals when loading completes, while keeping the timer-based transition intact.app/globals.css (1)
197-202: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winPalette duplicated six times as literal hex values.
The same 7-color palette (emerald/indigo/blue/amber/purple/red/slate) is repeated across
.pd-text-*,.pd-bg-*,.pd-icon-bg-*,.pd-icon-text-*,.pd-border-*, and.pd-dot-*. Since the PR also introduces aCOLORSmap intheme.tsfor the same palette, having two independent sources of truth risks the CSS and JS palettes drifting out of sync over time. Consider defining CSS custom properties (e.g.--pd-emerald,--pd-indigo, ...) once and referencing them across these rule groups.♻️ Example refactor sketch
:root { --pd-emerald: `#10b981`; --pd-emerald-bg: `#ecfdf5`; --pd-emerald-icon-bg: `#d1fae5`; --pd-emerald-text: `#047857`; --pd-emerald-border: `#a7f3d0`; /* ...repeat for indigo/blue/amber/purple/red/slate */ } .pd-text-emerald { color: var(--pd-emerald-text); } .pd-bg-emerald { background: var(--pd-emerald-bg); } .pd-icon-bg-emerald { background: var(--pd-emerald-icon-bg); } .pd-dot-emerald { background: var(--pd-emerald); }🤖 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 `@app/globals.css` around lines 197 - 202, The palette values are duplicated in the CSS utility classes, which creates a second source of truth alongside the new COLORS map in theme.ts. Refactor the pd-text-*, pd-bg-*, pd-icon-bg-*, pd-icon-text-*, pd-border-*, and pd-dot-* rules to reference shared CSS custom properties defined once for the emerald/indigo/blue/amber/purple/red/slate palette. Use the existing theme.ts COLORS symbols as the canonical palette source and align the globals.css variables/rules with them so both implementations stay in sync.components/pandora/AskPandoraHero.tsx (1)
4-6: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winDuplicated mock figures instead of centralized mock-data.
The evidence line hardcodes "11 memories • 6 preferences • 2 facts • Confidence 0.88" directly in JSX. The same figures (
11 memories • 6 preferences • 2 facts, confidence88) are independently hardcoded inAdaptiveProfileCard.tsx. Other components in this PR (StatCard,DiagnosticsCard) source their numbers frommock-data.ts(mockStats,coreSystems,gatedSystems), so this duplication breaks that pattern and risks the two components drifting out of sync if the mock values change.Consider moving these shared figures into
mock-data.tsand having both components read from it.🤖 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 `@components/pandora/AskPandoraHero.tsx` around lines 4 - 6, The evidence text in AskPandoraHero is hardcoding shared mock figures that are also duplicated elsewhere, so centralize those values in mock-data.ts and consume them from both AskPandoraHero and AdaptiveProfileCard. Update the AskPandoraHero component to read the memory/preference/fact counts and confidence from the shared mock source instead of embedding them in JSX, matching the existing pattern used by StatCard and DiagnosticsCard.components/pandora/AdaptiveProfileCard.tsx (1)
5-7: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winHardcoded profile data duplicates AskPandoraHero's mock values.
value={88},"11 memories • 6 preferences • 2 facts","Last refreshed 1h ago", and the traits array are inlined here, duplicating the same figures hardcoded inAskPandoraHero.tsx. UnlikeDiagnosticsCard(sourced fromcoreSystems/gatedSystems) orStatCard(sourced frommockStats), this component doesn't receive its content via props ormock-data.ts, making it inconsistent with the rest of the file's data-sourcing convention and prone to drifting from the hero card's numbers.🤖 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 `@components/pandora/AdaptiveProfileCard.tsx` around lines 5 - 7, AdaptiveProfileCard is hardcoding mock profile content that duplicates AskPandoraHero’s values, so move the ring value, summary text, last-refreshed text, and traits out of the component. Update AdaptiveProfileCard to accept this data via props or import it from mock-data.ts, and keep the rendering logic in AdaptiveProfileCard aligned with the data-sourcing pattern used by DiagnosticsCard and StatCard so it stays consistent with AskPandoraHero.components/pandora/Sidebar.tsx (1)
1-20: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winPositional icon↔label binding is fragile.
Icon assignment relies entirely on array-index parity between
iconsandnavItems. Any future reordering/insertion inmock-data.tswill silently desync icons from labels with no compile-time safety net. Consider keying icons by nav item name (e.g., aRecord<string, LucideIcon>map) so the association is explicit and resilient to reordering.🤖 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 `@components/pandora/Sidebar.tsx` around lines 1 - 20, The Sidebar icon lookup is currently tied to navItems array order, which makes the label-to-icon pairing fragile. Update Sidebar to use an explicit nav item to icon mapping instead of the icons array and index lookup, and reference the existing Sidebar, navItems, and Icon selection logic so each item resolves by name with a safe fallback like Circle. This should make the association resilient to reordering or inserting items in mock-data without changing the rendered icons unexpectedly.
🤖 Prompt for all review comments with 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.
Inline comments:
In `@app/globals.css`:
- Line 205: The mobile layout reserve space is hardcoded in the .pd-shell rule
while .pd-mobile-nav uses env(safe-area-inset-bottom), so the bottom padding can
be too small on devices with larger safe areas. Update the responsive styles in
globals.css so .pd-shell reserves bottom space using the same safe-area-aware
calculation as .pd-mobile-nav, keeping content clear of the fixed bottom nav on
all devices.
In `@components/pandora/MobileBottomNav.tsx`:
- Line 4: The Queue attention indicator in MobileBottomNav uses an <i> element
with aria-label, which may not be announced by assistive tech. Update the
MobileBottomNav rendering so the indicator uses an accessible element or role
that supports naming, such as a status element or a visually hidden text span,
while keeping the existing item/icon logic intact.
- Line 4: The `MobileBottomNav` active-state check has dead logic and misses the
mapped Feed selection: the `(item === "Dashboard" && activeNav === "Dashboard")`
clause in `MobileBottomNav` is redundant, and the `Feed` button never becomes
active because `onNavChange` sends `"Memory Feed"` while `active` still compares
against the raw `"Feed"` label. Update the `active` calculation in
`MobileBottomNav` so it matches the same value used by `onClick` for Feed (and
keep the remaining items comparing normally), and remove the unnecessary
Dashboard-specific condition.
---
Nitpick comments:
In `@app/globals.css`:
- Around line 197-202: The palette values are duplicated in the CSS utility
classes, which creates a second source of truth alongside the new COLORS map in
theme.ts. Refactor the pd-text-*, pd-bg-*, pd-icon-bg-*, pd-icon-text-*,
pd-border-*, and pd-dot-* rules to reference shared CSS custom properties
defined once for the emerald/indigo/blue/amber/purple/red/slate palette. Use the
existing theme.ts COLORS symbols as the canonical palette source and align the
globals.css variables/rules with them so both implementations stay in sync.
In `@components/pandora/AdaptiveProfileCard.tsx`:
- Around line 5-7: AdaptiveProfileCard is hardcoding mock profile content that
duplicates AskPandoraHero’s values, so move the ring value, summary text,
last-refreshed text, and traits out of the component. Update AdaptiveProfileCard
to accept this data via props or import it from mock-data.ts, and keep the
rendering logic in AdaptiveProfileCard aligned with the data-sourcing pattern
used by DiagnosticsCard and StatCard so it stays consistent with AskPandoraHero.
In `@components/pandora/AskPandoraHero.tsx`:
- Around line 4-6: The evidence text in AskPandoraHero is hardcoding shared mock
figures that are also duplicated elsewhere, so centralize those values in
mock-data.ts and consume them from both AskPandoraHero and AdaptiveProfileCard.
Update the AskPandoraHero component to read the memory/preference/fact counts
and confidence from the shared mock source instead of embedding them in JSX,
matching the existing pattern used by StatCard and DiagnosticsCard.
In `@components/pandora/mock-data.ts`:
- Around line 41-42: The nav item arrays are currently inferred as mutable
string arrays, which loses the literal union type needed by downstream
activeNav/onNavChange state. Update the navItems and mobileNavItems declarations
in mock-data.ts to readonly literal tuples using const assertion so their item
names can be reused as compile-time-safe unions. Keep the existing values the
same, but make the exported symbols infer literal types instead of string[].
In `@components/pandora/PandoraDashboard.tsx`:
- Around line 18-23: The loading state in PandoraDashboard is swapped in place
without any assistive announcement, so screen readers may miss when the
placeholders become real content. Update the PandoraDashboard component around
isSimulatingLoad/useEffect and the AdaptiveProfileCard/DiagnosticsCard render
path to add an aria-live region (or equivalent accessible status announcement)
that signals when loading completes, while keeping the timer-based transition
intact.
In `@components/pandora/Sidebar.tsx`:
- Around line 1-20: The Sidebar icon lookup is currently tied to navItems array
order, which makes the label-to-icon pairing fragile. Update Sidebar to use an
explicit nav item to icon mapping instead of the icons array and index lookup,
and reference the existing Sidebar, navItems, and Icon selection logic so each
item resolves by name with a safe fallback like Circle. This should make the
association resilient to reordering or inserting items in mock-data without
changing the rendered icons unexpectedly.
In `@components/pandora/types.ts`:
- Around line 16-26: The MemorySpace type currently hardcodes id to only
"real_life" | "au", which makes the shared mock data shape hard to extend.
Update MemorySpace.id in types.ts to be a more flexible identifier such as
string, or move the allowed values into a separately exported union that can be
intentionally maintained as new spaces are added. Keep the change scoped to the
MemorySpace definition so existing fields and consumers remain compatible.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 24cf00c0-2f9b-4b16-8574-5ce42ef16c99
⛔ Files ignored due to path filters (1)
package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (18)
app/globals.cssapp/pandora/page.tsxcomponents/pandora/AdaptiveProfileCard.tsxcomponents/pandora/AskPandoraHero.tsxcomponents/pandora/DiagnosticsCard.tsxcomponents/pandora/MemorySpacesCard.tsxcomponents/pandora/MobileBottomNav.tsxcomponents/pandora/PandoraDashboard.tsxcomponents/pandora/RecentEventsTimeline.tsxcomponents/pandora/Sidebar.tsxcomponents/pandora/Sparkline.tsxcomponents/pandora/StatCard.tsxcomponents/pandora/TopBar.tsxcomponents/pandora/WorkQueueCard.tsxcomponents/pandora/mock-data.tscomponents/pandora/theme.tscomponents/pandora/types.tspackage.json
| .pd-dot-emerald { background: #10b981; }.pd-dot-indigo { background: #6366f1; }.pd-dot-blue { background: #3b82f6; }.pd-dot-amber { background: #f59e0b; }.pd-dot-purple { background: #a855f7; }.pd-dot-red { background: #ef4444; }.pd-dot-slate { background: #64748b; } | ||
| .pd-mobile-nav { display: none; } | ||
| @media (max-width: 1200px) { .pd-stat-grid { grid-template-columns: repeat(2,minmax(0,1fr)); } .pd-dashboard-grid { grid-template-columns: 1fr; } } | ||
| @media (max-width: 900px) { .pd-shell { display: block; padding-bottom: 86px; } .pd-sidebar { display: none; } .pd-mobile-brand { display: flex; } .pd-topbar { align-items: stretch; flex-direction: column; } .pd-top-actions { justify-content: space-between; } .pd-header-row { align-items: flex-start; flex-direction: column; } .pd-mobile-nav { align-items: center; background: rgba(255,255,255,.94); border-top: 1px solid #e2e8f0; bottom: 0; display: grid; grid-template-columns: repeat(5,1fr); left: 0; padding: 10px 10px calc(env(safe-area-inset-bottom) + .75rem); position: fixed; right: 0; z-index: 30; } .pd-mobile-nav button { background: transparent; border: 0; color: #64748b; display: grid; font-size: .72rem; font-weight: 900; gap: 3px; place-items: center; } .pd-mobile-nav span { position: relative; } .pd-mobile-nav i { background: #f59e0b; border-radius: 999px; height: 7px; position: absolute; right: -5px; top: -3px; width: 7px; } } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Fixed padding-bottom doesn't account for device safe-area inset.
.pd-mobile-nav height grows with calc(env(safe-area-inset-bottom) + .75rem), but .pd-shell's reserved padding-bottom: 86px is 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
-@media (max-width: 900px) { .pd-shell { display: block; padding-bottom: 86px; } ...
+@media (max-width: 900px) { .pd-shell { display: block; padding-bottom: calc(86px + env(safe-area-inset-bottom)); } ...📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| @media (max-width: 900px) { .pd-shell { display: block; padding-bottom: 86px; } .pd-sidebar { display: none; } .pd-mobile-brand { display: flex; } .pd-topbar { align-items: stretch; flex-direction: column; } .pd-top-actions { justify-content: space-between; } .pd-header-row { align-items: flex-start; flex-direction: column; } .pd-mobile-nav { align-items: center; background: rgba(255,255,255,.94); border-top: 1px solid #e2e8f0; bottom: 0; display: grid; grid-template-columns: repeat(5,1fr); left: 0; padding: 10px 10px calc(env(safe-area-inset-bottom) + .75rem); position: fixed; right: 0; z-index: 30; } .pd-mobile-nav button { background: transparent; border: 0; color: #64748b; display: grid; font-size: .72rem; font-weight: 900; gap: 3px; place-items: center; } .pd-mobile-nav span { position: relative; } .pd-mobile-nav i { background: #f59e0b; border-radius: 999px; height: 7px; position: absolute; right: -5px; top: -3px; width: 7px; } } | |
| `@media` (max-width: 900px) { .pd-shell { display: block; padding-bottom: calc(86px + env(safe-area-inset-bottom)); } .pd-sidebar { display: none; } .pd-mobile-brand { display: flex; } .pd-topbar { align-items: stretch; flex-direction: column; } .pd-top-actions { justify-content: space-between; } .pd-header-row { align-items: flex-start; flex-direction: column; } .pd-mobile-nav { align-items: center; background: rgba(255,255,255,.94); border-top: 1px solid `#e2e8f0`; bottom: 0; display: grid; grid-template-columns: repeat(5,1fr); left: 0; padding: 10px 10px calc(env(safe-area-inset-bottom) + .75rem); position: fixed; right: 0; z-index: 30; } .pd-mobile-nav button { background: transparent; border: 0; color: `#64748b`; display: grid; font-size: .72rem; font-weight: 900; gap: 3px; place-items: center; } .pd-mobile-nav span { position: relative; } .pd-mobile-nav i { background: `#f59e0b`; border-radius: 999px; height: 7px; position: absolute; right: -5px; top: -3px; width: 7px; } } |
🤖 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 `@app/globals.css` at line 205, The mobile layout reserve space is hardcoded in
the .pd-shell rule while .pd-mobile-nav uses env(safe-area-inset-bottom), so the
bottom padding can be too small on devices with larger safe areas. Update the
responsive styles in globals.css so .pd-shell reserves bottom space using the
same safe-area-aware calculation as .pd-mobile-nav, keeping content clear of the
fixed bottom nav on all devices.
| 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.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
<i> element with aria-label may not be exposed to assistive tech.
<i> has no implicit ARIA role (generic), so an aria-label on it is not guaranteed to be announced by screen readers. Use an element/role that supports accessible naming, e.g. role="status" or a visually-hidden text span.
♿ 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
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| 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>; } | |
| 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" ? <span className="pd-badge-dot" role="status" aria-label="Queue has attention items" /> : null}</span>{item}</button>; })}</nav>; } |
🤖 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 `@components/pandora/MobileBottomNav.tsx` at line 4, The Queue attention
indicator in MobileBottomNav uses an <i> element with aria-label, which may not
be announced by assistive tech. Update the MobileBottomNav rendering so the
indicator uses an accessible element or role that supports naming, such as a
status element or a visually hidden text span, while keeping the existing
item/icon logic intact.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Redundant active condition; "Feed" nav item never shows active state.
The clause (item === "Dashboard" && activeNav === "Dashboard") is fully subsumed by item === activeNav and is dead logic. Meanwhile, onClick maps the "Feed" item to onNavChange("Memory Feed"), but active only compares item === activeNav — since item is "Feed" and activeNav becomes "Memory Feed" after the click, the Feed button will never render as active once selected.
🐛 Proposed fix
-const active = item === activeNav || (item === "Dashboard" && activeNav === "Dashboard");
+const active = item === "Feed" ? activeNav === "Memory Feed" : item === activeNav;📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| 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>; } | |
| 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 === "Feed" ? activeNav === "Memory Feed" : item === activeNav; 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>; } |
🤖 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 `@components/pandora/MobileBottomNav.tsx` at line 4, The `MobileBottomNav`
active-state check has dead logic and misses the mapped Feed selection: the
`(item === "Dashboard" && activeNav === "Dashboard")` clause in
`MobileBottomNav` is redundant, and the `Feed` button never becomes active
because `onNavChange` sends `"Memory Feed"` while `active` still compares
against the raw `"Feed"` label. Update the `active` calculation in
`MobileBottomNav` so it matches the same value used by `onClick` for Feed (and
keep the remaining items comparing normally), and remove the unnecessary
Dashboard-specific condition.
Motivation
Description
app/pandora/page.tsxthat renders the clientPandoraDashboardshell and wired responsive desktop sidebar + mobile bottom nav.components/pandora/(Dashboard, Sidebar, TopBar, AskPandoraHero, StatCard, WorkQueueCard, MemorySpacesCard, RecentEventsTimeline, AdaptiveProfileCard, DiagnosticsCard, MobileBottomNav, Sparkline) plustheme.ts,types.ts, andmock-data.ts.COLORStheme map and utilitycn()to avoid dynamic class interpolation and ensure fixed Tailwind-like class strings, and extendedapp/globals.csswith dashboard styles.lucide-reacttopackage.jsonfor icons and kept all data mock-only; no backend/API wiring, no schema/migration changes, and no semantic/model/embedding/pruning logic added.Testing
npm run typecheckwhich passed with no type errors.npm run lintwhich completed; it surfaced pre-existing unrelated warnings inlib/db/core-repositories.tsandvitest.config.tsbut no new errors.npm run testwhere the test suite completed successfully (88 files, 569 testspassed).npm run buildwhich succeeded; build emitted pre-existing Next/Supabase edge/runtime warnings but completed.npm run env:policywhich passed; attempted a Playwright screenshot but browser download failed (ERR_SOCKET_CLOSED) so no screenshot artifact was produced.Codex Task
Summary by CodeRabbit
New Features
UI/Responsive Updates