feat: restore live Pandora dashboard#132
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
📝 WalkthroughWalkthroughPandora dashboard components (AdaptiveProfileCard, AskPandoraHero, DiagnosticsCard, MemorySpacesCard, RecentEventsTimeline, WorkQueueCard, PandoraDashboard) are converted from hardcoded mock rendering to prop-driven rendering. The backing service adds warning-collecting data reads and helper functions. Mock fixtures are restructured, and new unit/guard tests are added. ChangesLive Pandora Dashboard Data
Estimated code review effort: 3 (Moderate) | ~25 minutes Sequence Diagram(s)sequenceDiagram
participant PandoraDashboard
participant loadPandoraDashboardData
participant SupabaseClient
participant Cards as Pandora cards
PandoraDashboard->>loadPandoraDashboardData: request dashboard data
loadPandoraDashboardData->>SupabaseClient: read tables per namespace
SupabaseClient-->>loadPandoraDashboardData: rows or error
loadPandoraDashboardData->>loadPandoraDashboardData: collect warnings, format confidence/summary
loadPandoraDashboardData-->>PandoraDashboard: PandoraDashboardData
PandoraDashboard->>Cards: pass profile/hero/diagnostics/spaces/events props
Cards-->>PandoraDashboard: rendered live UI
Compact metadata
Poem 🚥 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: 3015527b75
ℹ️ 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".
| { 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>; |
There was a problem hiding this comment.
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 👍 / 👎.
| <div className="pd-dashboard-grid"> | ||
| <div className="pd-dashboard-col pd-dashboard-col-wide"> | ||
| <MemorySpacesCard spaces={dashboardData.memorySpaces} /> | ||
| <RecentEventsTimeline events={dashboardData.timelineEvents} /> |
There was a problem hiding this comment.
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 👍 / 👎.
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (5)
components/pandora/mock-data.ts (1)
6-8: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueMinor duplication between
mockStatsandfixtureDashboardData.stats.The single stat object in
fixtureDashboardData.stats(Line 19) re-literalizes the same values asmockStats(Lines 6-8), just omittingicon. SincePandoraDashboardData.statsis typed asDashboardStatData[](noiconrequired), you could derive it frommockStatsinstead of duplicating the literal.♻️ Suggested refactor
-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 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: mockStats.map(({ icon, ...rest }) => rest), memorySpaces, workQueue, profileSnapshot, timelineEvents, diagnostics: { coreSystems, gatedSystems, envelope: { title: "Fixture", description: "Mock only: no live data." } } };Also applies to: 19-19
🤖 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 6 - 8, `fixtureDashboardData.stats` is duplicating the same stat values already defined in `mockStats`, so refactor it to derive the stats array from `mockStats` instead of re-literalizing the object. Use the existing `mockStats` symbol in `components/pandora/mock-data.ts` and adapt it to `DashboardStatData[]` by omitting the `icon` field, keeping the shared fixture values in one place.tests/unit/pandora-dashboard-guard.test.ts (2)
4-4: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick winMock-data guard doesn't cover the other converted card components.
productionFilesonly checksapp/pandora/page.tsxandPandoraDashboard.tsx, but this cohort also convertsAdaptiveProfileCard,AskPandoraHero,DiagnosticsCard,MemorySpacesCard,RecentEventsTimeline, andWorkQueueCardoff mock rendering. A regression reintroducing amock-dataimport in any of those cards would not be caught by this guard.♻️ Proposed fix
-const productionFiles = ["app/pandora/page.tsx", "components/pandora/PandoraDashboard.tsx"]; +const productionFiles = [ + "app/pandora/page.tsx", + "components/pandora/PandoraDashboard.tsx", + "components/pandora/AdaptiveProfileCard.tsx", + "components/pandora/AskPandoraHero.tsx", + "components/pandora/DiagnosticsCard.tsx", + "components/pandora/MemorySpacesCard.tsx", + "components/pandora/RecentEventsTimeline.tsx", + "components/pandora/WorkQueueCard.tsx", +];Also applies to: 7-9
🤖 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 `@tests/unit/pandora-dashboard-guard.test.ts` at line 4, The mock-data guard in the Pandora dashboard test is too narrow because productionFiles only covers app/pandora/page.tsx and PandoraDashboard, so regressions in the other converted cards can slip through. Expand the productionFiles list in pandora-dashboard-guard.test.ts to include AdaptiveProfileCard, AskPandoraHero, DiagnosticsCard, MemorySpacesCard, RecentEventsTimeline, and WorkQueueCard, and keep the guard logic the same so it flags any reintroduced mock-data import in those components.
13-14: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winOverbroad
searchParamsban makes Line 14 unreachable and risks future false failures.Line 13 fails the test on any occurrence of
searchParamsinpage.tsx, not just user_id-related usage, so Line 14's more targeted regex can never independently trigger. This will also break the guard test ifsearchParamsis legitimately used later for unrelated purposes (pagination, tabs, etc.).♻️ Proposed fix
- expect(page).not.toMatch(/searchParams/); - expect(page).not.toMatch(/searchParams.*user_id|searchParams.*userId|query.*user_id|query.*userId/s); + expect(page).not.toMatch(/searchParams.*user_id|searchParams.*userId|query.*user_id|query.*userId/s);🤖 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 `@tests/unit/pandora-dashboard-guard.test.ts` around lines 13 - 14, The guard test in pandora-dashboard-guard.test.ts is too broad because the generic searchParams assertion makes the more specific user_id/userId check unreachable. Update the test to remove the blanket searchParams ban and keep only the targeted regex that rejects searchParams/query usage tied to user_id or userId, so unrelated future uses in page.tsx do not fail the test. Refer to the existing expect(page) assertions in the test file when adjusting the checks.tests/unit/pandora-dashboard-ui.test.tsx (1)
22-33: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueConsider testing-library over raw HTML substring assertions (optional).
Static-markup substring checks work but are brittle to copy/markup changes and don't verify DOM structure/accessibility.
@testing-library/reactwithrender/screenwould give more resilient, semantic assertions if this suite grows.🤖 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 `@tests/unit/pandora-dashboard-ui.test.tsx` around lines 22 - 33, The PandoraDashboard test currently relies on raw static HTML substring checks, which is brittle and doesn’t verify the rendered DOM semantically. Update the suite around PandoraDashboard to use testing-library patterns such as render and screen queries instead of renderToStaticMarkup, and assert against visible text/roles using the existing dashboardData fixture so the test remains stable if markup or copy formatting changes.components/pandora/PandoraDashboard.tsx (1)
17-21: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winIcon assignment is positionally coupled to
dashboardData.statsorder.
statIcons[index]ties each icon to the stat array's index rather than itsid. If the service ever reorders/add/removes a stat (e.g.,lib/services/pandora-dashboard-service.ts'sstatsarray), icons will silently mismatch their stat without any type error.♻️ Proposed fix: map icons by stat id
-const statIcons = [History, Package, ShieldCheck, GitBranch, Archive, ClipboardList]; +const statIconsById: Record<string, LucideIcon> = { + events: History, + packs: Package, + reviewed: ShieldCheck, + loops: GitBranch, + retrieval: Archive, + queue: ClipboardList, +}; export function PandoraDashboard({ dashboardData }: { dashboardData: PandoraDashboardData }) { const [activeNav, setActiveNav] = useState("Dashboard"); - const stats: StatItem[] = dashboardData.stats.map((stat, index) => ({ ...stat, icon: statIcons[index] ?? Package })); + const stats: StatItem[] = dashboardData.stats.map((stat) => ({ ...stat, icon: statIconsById[stat.id] ?? Package }));(Import
LucideIcontype fromlucide-reactfor the record annotation.)🤖 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 17 - 21, The icon selection in PandoraDashboard is coupled to array position via statIcons[index], so update it to assign icons by each stat’s id instead of the stats order. Add a stat-id-to-icon mapping in PandoraDashboard, type it with LucideIcon from lucide-react, and use dashboardData.stats.map to look up the icon from that map when building stats so reordering or changing the service stats array does not break icon assignment.
🤖 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 `@lib/services/pandora-dashboard-service.ts`:
- Around line 39-48: The per-namespace reads in pandora-dashboard-service are
still being executed sequentially inside the Promise.all mapper, which makes
each namespace wait on 7 separate rows() round-trips. Update the mapping logic
around the namespace payload assembly so the rows() calls for memory_events,
memory_context_packs, memory_profiles, memory_open_loops,
memory_capture_candidates, memory_review_queue_items, and
memory_pruning_candidates are started concurrently and awaited together for each
namespace. Keep the outer Promise.all over namespaces, but use parallel fetching
within the namespace block to reduce request latency.
- Line 83: The Recent Memory Events timeline in pandora-dashboard-service is
biased because the merged events array is sliced before being globally ordered.
Update the timelineEvents logic so the combined events from the flatMap result
are sorted by recency using created_at in the service flow before applying
slice(0, 6), ensuring both real_life and au items compete fairly; keep the
mapping logic in pandora-dashboard-service unchanged aside from using the
correctly ordered events list.
---
Nitpick comments:
In `@components/pandora/mock-data.ts`:
- Around line 6-8: `fixtureDashboardData.stats` is duplicating the same stat
values already defined in `mockStats`, so refactor it to derive the stats array
from `mockStats` instead of re-literalizing the object. Use the existing
`mockStats` symbol in `components/pandora/mock-data.ts` and adapt it to
`DashboardStatData[]` by omitting the `icon` field, keeping the shared fixture
values in one place.
In `@components/pandora/PandoraDashboard.tsx`:
- Around line 17-21: The icon selection in PandoraDashboard is coupled to array
position via statIcons[index], so update it to assign icons by each stat’s id
instead of the stats order. Add a stat-id-to-icon mapping in PandoraDashboard,
type it with LucideIcon from lucide-react, and use dashboardData.stats.map to
look up the icon from that map when building stats so reordering or changing the
service stats array does not break icon assignment.
In `@tests/unit/pandora-dashboard-guard.test.ts`:
- Line 4: The mock-data guard in the Pandora dashboard test is too narrow
because productionFiles only covers app/pandora/page.tsx and PandoraDashboard,
so regressions in the other converted cards can slip through. Expand the
productionFiles list in pandora-dashboard-guard.test.ts to include
AdaptiveProfileCard, AskPandoraHero, DiagnosticsCard, MemorySpacesCard,
RecentEventsTimeline, and WorkQueueCard, and keep the guard logic the same so it
flags any reintroduced mock-data import in those components.
- Around line 13-14: The guard test in pandora-dashboard-guard.test.ts is too
broad because the generic searchParams assertion makes the more specific
user_id/userId check unreachable. Update the test to remove the blanket
searchParams ban and keep only the targeted regex that rejects
searchParams/query usage tied to user_id or userId, so unrelated future uses in
page.tsx do not fail the test. Refer to the existing expect(page) assertions in
the test file when adjusting the checks.
In `@tests/unit/pandora-dashboard-ui.test.tsx`:
- Around line 22-33: The PandoraDashboard test currently relies on raw static
HTML substring checks, which is brittle and doesn’t verify the rendered DOM
semantically. Update the suite around PandoraDashboard to use testing-library
patterns such as render and screen queries instead of renderToStaticMarkup, and
assert against visible text/roles using the existing dashboardData fixture so
the test remains stable if markup or copy formatting changes.
🪄 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: b9f2d202-43a6-4567-ab79-b1b123e99836
📒 Files selected for processing (12)
components/pandora/AdaptiveProfileCard.tsxcomponents/pandora/AskPandoraHero.tsxcomponents/pandora/DiagnosticsCard.tsxcomponents/pandora/MemorySpacesCard.tsxcomponents/pandora/PandoraDashboard.tsxcomponents/pandora/RecentEventsTimeline.tsxcomponents/pandora/WorkQueueCard.tsxcomponents/pandora/mock-data.tslib/services/pandora-dashboard-service.tstests/unit/pandora-dashboard-guard.test.tstests/unit/pandora-dashboard-service.test.tstests/unit/pandora-dashboard-ui.test.tsx
| const data = await Promise.all(namespaces.map(async (namespace) => ({ | ||
| namespace, | ||
| events: await rows(client, "memory_events", input.userId, namespace, 500), | ||
| packs: await rows(client, "memory_context_packs", input.userId, namespace, 50), | ||
| profiles: await rows(client, "memory_profiles", input.userId, namespace, 20), | ||
| loops: await rows(client, "memory_open_loops", input.userId, namespace, 100), | ||
| candidates: await rows(client, "memory_capture_candidates", input.userId, namespace, 100), | ||
| review: await rows(client, "memory_review_queue_items", input.userId, namespace, 100), | ||
| pruning: await rows(client, "memory_pruning_candidates", input.userId, namespace, 100), | ||
| events: await rows(client, "memory_events", input.userId, namespace, warnings, 500), | ||
| packs: await rows(client, "memory_context_packs", input.userId, namespace, warnings, 50), | ||
| profiles: await rows(client, "memory_profiles", input.userId, namespace, warnings, 20), | ||
| loops: await rows(client, "memory_open_loops", input.userId, namespace, warnings, 100), | ||
| candidates: await rows(client, "memory_capture_candidates", input.userId, namespace, warnings, 100), | ||
| review: await rows(client, "memory_review_queue_items", input.userId, namespace, warnings, 100), | ||
| pruning: await rows(client, "memory_pruning_candidates", input.userId, namespace, warnings, 100), | ||
| }))); |
There was a problem hiding this comment.
🚀 Performance & Scalability | 🟠 Major | ⚡ Quick win
Parallelize per-namespace table reads.
For each namespace, the 7 rows() calls are awaited sequentially (only the two namespaces run in parallel via the outer Promise.all). This serializes 7 round-trips per namespace on the dashboard's request path, needlessly inflating load latency.
⚡ Proposed fix: fire per-namespace reads concurrently
- const data = await Promise.all(namespaces.map(async (namespace) => ({
- namespace,
- events: await rows(client, "memory_events", input.userId, namespace, warnings, 500),
- packs: await rows(client, "memory_context_packs", input.userId, namespace, warnings, 50),
- profiles: await rows(client, "memory_profiles", input.userId, namespace, warnings, 20),
- loops: await rows(client, "memory_open_loops", input.userId, namespace, warnings, 100),
- candidates: await rows(client, "memory_capture_candidates", input.userId, namespace, warnings, 100),
- review: await rows(client, "memory_review_queue_items", input.userId, namespace, warnings, 100),
- pruning: await rows(client, "memory_pruning_candidates", input.userId, namespace, warnings, 100),
- })));
+ const data = await Promise.all(namespaces.map(async (namespace) => {
+ const [events, packs, profiles, loops, candidates, review, pruning] = await Promise.all([
+ rows(client, "memory_events", input.userId, namespace, warnings, 500),
+ rows(client, "memory_context_packs", input.userId, namespace, warnings, 50),
+ rows(client, "memory_profiles", input.userId, namespace, warnings, 20),
+ rows(client, "memory_open_loops", input.userId, namespace, warnings, 100),
+ rows(client, "memory_capture_candidates", input.userId, namespace, warnings, 100),
+ rows(client, "memory_review_queue_items", input.userId, namespace, warnings, 100),
+ rows(client, "memory_pruning_candidates", input.userId, namespace, warnings, 100),
+ ]);
+ return { namespace, events, packs, profiles, loops, candidates, review, pruning };
+ }));📝 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.
| const data = await Promise.all(namespaces.map(async (namespace) => ({ | |
| namespace, | |
| events: await rows(client, "memory_events", input.userId, namespace, 500), | |
| packs: await rows(client, "memory_context_packs", input.userId, namespace, 50), | |
| profiles: await rows(client, "memory_profiles", input.userId, namespace, 20), | |
| loops: await rows(client, "memory_open_loops", input.userId, namespace, 100), | |
| candidates: await rows(client, "memory_capture_candidates", input.userId, namespace, 100), | |
| review: await rows(client, "memory_review_queue_items", input.userId, namespace, 100), | |
| pruning: await rows(client, "memory_pruning_candidates", input.userId, namespace, 100), | |
| events: await rows(client, "memory_events", input.userId, namespace, warnings, 500), | |
| packs: await rows(client, "memory_context_packs", input.userId, namespace, warnings, 50), | |
| profiles: await rows(client, "memory_profiles", input.userId, namespace, warnings, 20), | |
| loops: await rows(client, "memory_open_loops", input.userId, namespace, warnings, 100), | |
| candidates: await rows(client, "memory_capture_candidates", input.userId, namespace, warnings, 100), | |
| review: await rows(client, "memory_review_queue_items", input.userId, namespace, warnings, 100), | |
| pruning: await rows(client, "memory_pruning_candidates", input.userId, namespace, warnings, 100), | |
| }))); | |
| const data = await Promise.all(namespaces.map(async (namespace) => { | |
| const [events, packs, profiles, loops, candidates, review, pruning] = await Promise.all([ | |
| rows(client, "memory_events", input.userId, namespace, warnings, 500), | |
| rows(client, "memory_context_packs", input.userId, namespace, warnings, 50), | |
| rows(client, "memory_profiles", input.userId, namespace, warnings, 20), | |
| rows(client, "memory_open_loops", input.userId, namespace, warnings, 100), | |
| rows(client, "memory_capture_candidates", input.userId, namespace, warnings, 100), | |
| rows(client, "memory_review_queue_items", input.userId, namespace, warnings, 100), | |
| rows(client, "memory_pruning_candidates", input.userId, namespace, warnings, 100), | |
| ]); | |
| return { namespace, events, packs, profiles, loops, candidates, review, pruning }; | |
| })); |
🤖 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 `@lib/services/pandora-dashboard-service.ts` around lines 39 - 48, The
per-namespace reads in pandora-dashboard-service are still being executed
sequentially inside the Promise.all mapper, which makes each namespace wait on 7
separate rows() round-trips. Update the mapping logic around the namespace
payload assembly so the rows() calls for memory_events, memory_context_packs,
memory_profiles, memory_open_loops, memory_capture_candidates,
memory_review_queue_items, and memory_pruning_candidates are started
concurrently and awaited together for each namespace. Keep the outer Promise.all
over namespaces, but use parallel fetching within the namespace block to reduce
request latency.
| timelineEvents: events.slice(0, 6).map((event) => ({ id: event.id, title: `${event.namespace} • ${event.status}`, time: event.created_at ?? "Live read", desc: event.extracted_summary ?? event.raw_text ?? "No summary", namespace: event.namespace, color: event.namespace === "real_life" ? "emerald" : "purple" })), | ||
| diagnostics: { coreSystems: [{ label: "Route exposure", value: "Auth gated", state: "healthy" }, { label: "Displayed data", value: "Live reads", state: "healthy" }, { label: "Master-pack invariant", value: duplicates ? `${duplicates} duplicate` : "OK", state: duplicates ? "attention" : "healthy" }, { label: "Client user_id", value: "Rejected", state: "healthy" }], gatedSystems: [{ label: "Semantic retrieval", value: "Gated Off", state: "gated" }, { label: "Embeddings", value: "Gated Off", state: "gated" }, { label: "Model calls", value: "Gated Off", state: "gated" }, { label: "Pruning automation", value: "Review-only", state: "gated" }], envelope: { title: "Dashboard Truth Envelope", description: "Live loader completed from authenticated Supabase reads." } }, | ||
| profileSnapshot: { name: profile?.title ?? "No active profile", status: profile ? "Live read" : "No live data", confidencePercent: confidence.percent, confidenceLabel: confidence.label, summary: profile?.summary ?? "No active adaptive profile returned for this session.", lastRefreshed: profile?.updated_at ?? "No profile timestamp returned", traits: ["Authenticated", "RLS scoped"], evidence: profile?.id ? `Live profile row ${profile.id}` : "No active profile row" }, | ||
| timelineEvents: events.slice(0, 6).map((event) => ({ id: String(event.id ?? `${event.namespace}-${event.created_at ?? "event"}`), title: `${event.namespace} • ${event.status ?? "unknown"}`, time: event.created_at ?? "Live read", desc: eventSummary(event), namespace: event.namespace === "au" ? "au" : "real_life", color: event.namespace === "au" ? "purple" : "emerald" })), |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Timeline is biased toward real_life events — merged array isn't re-sorted before slicing.
events = data.flatMap((item) => item.events) concatenates the already-sorted real_life array followed by the au array; it is not globally sorted by recency. events.slice(0, 6) then takes the first 6 items of this concatenation, which will only include au events if real_life has fewer than 6 rows — regardless of actual timestamps. "Recent Memory Events" can therefore omit genuinely more-recent au events.
🐛 Proposed fix: sort merged events by recency before slicing
- timelineEvents: events.slice(0, 6).map((event) => ({ id: String(event.id ?? `${event.namespace}-${event.created_at ?? "event"}`), title: `${event.namespace} • ${event.status ?? "unknown"}`, time: event.created_at ?? "Live read", desc: eventSummary(event), namespace: event.namespace === "au" ? "au" : "real_life", color: event.namespace === "au" ? "purple" : "emerald" })),
+ timelineEvents: [...events]
+ .sort((a, b) => new Date(b.created_at ?? 0).getTime() - new Date(a.created_at ?? 0).getTime())
+ .slice(0, 6)
+ .map((event) => ({ id: String(event.id ?? `${event.namespace}-${event.created_at ?? "event"}`), title: `${event.namespace} • ${event.status ?? "unknown"}`, time: event.created_at ?? "Live read", desc: eventSummary(event), namespace: event.namespace === "au" ? "au" : "real_life", color: event.namespace === "au" ? "purple" : "emerald" })),📝 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.
| timelineEvents: events.slice(0, 6).map((event) => ({ id: String(event.id ?? `${event.namespace}-${event.created_at ?? "event"}`), title: `${event.namespace} • ${event.status ?? "unknown"}`, time: event.created_at ?? "Live read", desc: eventSummary(event), namespace: event.namespace === "au" ? "au" : "real_life", color: event.namespace === "au" ? "purple" : "emerald" })), | |
| timelineEvents: [...events] | |
| .sort((a, b) => new Date(b.created_at ?? 0).getTime() - new Date(a.created_at ?? 0).getTime()) | |
| .slice(0, 6) | |
| .map((event) => ({ id: String(event.id ?? `${event.namespace}-${event.created_at ?? "event"}`), title: `${event.namespace} • ${event.status ?? "unknown"}`, time: event.created_at ?? "Live read", desc: eventSummary(event), namespace: event.namespace === "au" ? "au" : "real_life", color: event.namespace === "au" ? "purple" : "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 `@lib/services/pandora-dashboard-service.ts` at line 83, The Recent Memory
Events timeline in pandora-dashboard-service is biased because the merged events
array is sliced before being globally ordered. Update the timelineEvents logic
so the combined events from the flatMap result are sorted by recency using
created_at in the service flow before applying slice(0, 6), ensuring both
real_life and au items compete fairly; keep the mapping logic in
pandora-dashboard-service unchanged aside from using the correctly ordered
events list.
Motivation
real_lifeandauremain strictly separated and client-supplieduser_idoverrides are rejected.Description
PandoraDashboardrenders a full three-column layout and passes live props into cards instead of relying on mock fixtures.AskPandoraHero(hero/evidence/warnings),MemorySpacesCard(MemorySpace[]),RecentEventsTimeline(TimelineEventData[]),WorkQueueCard(WorkQueueData),AdaptiveProfileCard(ProfileSnapshot), andDiagnosticsCard(diagnostics object).lib/services/pandora-dashboard-service.tsto queryreal_lifeandauexplicitly with server-deriveduserId, collect readwarnings, return stable empty states on read failures, surface duplicate active master packs, keep retrieval eval explicitly labeledGated, safely format profile confidence from multiple input shapes, and render null-safe timeline summaries.components/pandora/mock-data.tsas a neutral dev/story fixture only (wording changed to “Fixture”, “Mock only”, “No live data”) and removed its use from the/pandoraproduction path.user_idoverrides.Testing
npm run typecheck— passed.npm run lint— passed with pre-existing warnings (non-blocking).npm run test— all tests passed (92 files / 590 tests).npm run build— build completed successfully with existing Next/Supabase warnings noted but no failures.npm run env:policy— passed.Codex Task
Summary by CodeRabbit
New Features
Bug Fixes