Skip to content

feat: restore live Pandora dashboard#132

Merged
besfeng23 merged 1 commit into
mainfrom
codex/upgrade-pandora-dashboard-to-production-quality
Jul 3, 2026
Merged

feat: restore live Pandora dashboard#132
besfeng23 merged 1 commit into
mainfrom
codex/upgrade-pandora-dashboard-to-production-quality

Conversation

@besfeng23

@besfeng23 besfeng23 commented Jul 3, 2026

Copy link
Copy Markdown
Owner

Motivation

  • Replace the minimal mock snapshot with a production-quality, auth-gated operator dashboard that displays real, server-derived Supabase state while preserving Pandora safety gates.
  • Ensure namespace isolation and server-scoped identity so real_life and au remain strictly separated and client-supplied user_id overrides are rejected.
  • Harden loader and UI to fail safe when table reads are missing or unreadable and to avoid any fabricated retrieval/accuracy claims.

Description

  • Refactored dashboard composition so PandoraDashboard renders a full three-column layout and passes live props into cards instead of relying on mock fixtures.
  • Updated components to accept live props: AskPandoraHero (hero/evidence/warnings), MemorySpacesCard (MemorySpace[]), RecentEventsTimeline (TimelineEventData[]), WorkQueueCard (WorkQueueData), AdaptiveProfileCard (ProfileSnapshot), and DiagnosticsCard (diagnostics object).
  • Hardened lib/services/pandora-dashboard-service.ts to query real_life and au explicitly with server-derived userId, collect read warnings, return stable empty states on read failures, surface duplicate active master packs, keep retrieval eval explicitly labeled Gated, safely format profile confidence from multiple input shapes, and render null-safe timeline summaries.
  • Kept components/pandora/mock-data.ts as a neutral dev/story fixture only (wording changed to “Fixture”, “Mock only”, “No live data”) and removed its use from the /pandora production path.
  • Added unit and component tests and production guard tests covering user isolation, namespace separation, duplicate active master detection, missing-table warnings/empty-state behavior, gated retrieval eval, profile confidence formatting, null timeline summaries, UI rendering of live props, and guards against importing mock-data or accepting client user_id overrides.

Testing

  • Ran typecheck: npm run typecheck — passed.
  • Ran lint: npm run lint — passed with pre-existing warnings (non-blocking).
  • Ran unit/component tests: npm run test — all tests passed (92 files / 590 tests).
  • Built the app: npm run build — build completed successfully with existing Next/Supabase warnings noted but no failures.
  • Env policy check: npm run env:policy — passed.

Codex Task

Summary by CodeRabbit

  • New Features

    • Dashboard sections now show live, data-driven content instead of static mock content.
    • Added clearer live status, operator summary, recent events, memory spaces, profile details, and diagnostics messaging.
    • Confidence and timeline displays now handle missing or invalid data more gracefully.
  • Bug Fixes

    • Improved stability when dashboard data is incomplete or unavailable.
    • Prevented invalid confidence ring rendering and added empty-state messaging for missing events.

@vercel

vercel Bot commented Jul 3, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
memory Ready Ready Preview, Comment Jul 3, 2026 7:43pm

@coderabbitai

coderabbitai Bot commented Jul 3, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Pandora 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.

Changes

Live Pandora Dashboard Data

Layer / File(s) Summary
Dashboard service data aggregation
lib/services/pandora-dashboard-service.ts
Adds a warnings-collecting rows helper, NamespaceRead type, activeMasters/safeArrayLength/formatConfidence/eventSummary helpers, and derives live, evidence, stats, memory spaces, timeline events, and diagnostics based on warning state.
Prop-driven card components
components/pandora/AdaptiveProfileCard.tsx, components/pandora/AskPandoraHero.tsx, components/pandora/DiagnosticsCard.tsx, components/pandora/MemorySpacesCard.tsx, components/pandora/RecentEventsTimeline.tsx, components/pandora/WorkQueueCard.tsx
Components now accept typed data props (profile, hero/evidence/warnings, diagnostics, spaces, events) instead of importing mock constants; ConfidenceRing clamps confidence to a finite [0,100] range.
Dashboard composition
components/pandora/PandoraDashboard.tsx
Derives per-stat icons from a statIcons array, removes the old hero/snapshot sections, and adds AskPandoraHero plus a grid of the updated cards with an Operator Boundary summary.
Mock fixture restructuring
components/pandora/mock-data.ts
Updates fixture data (profileSnapshot, mockStats, memorySpaces, timelineEvents, coreSystems/gatedSystems) to "No live data" semantics and adds a new fixtureDashboardData export.
Service and UI tests
tests/unit/pandora-dashboard-service.test.ts, tests/unit/pandora-dashboard-ui.test.tsx
Adds tests for simulated table failures, confidence formatting, null summary fallback, and rendered dashboard HTML output.
Production dashboard guards
tests/unit/pandora-dashboard-guard.test.ts
New test file asserting production Pandora files avoid mock-data imports, searchParams-based user IDs, and hardcoded fake accuracy claims.

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
Loading

Compact metadata

  • Related issues: Not specified in the provided diff.
  • Related PRs: Not specified in the provided diff.
  • Suggested labels: frontend, pandora-dashboard
  • Suggested reviewers: Not specified in the provided diff.

Poem
A rabbit hopped past mock and stub,
Found live data waiting in the hub,
Cards once fixed now bend and sway,
Warnings whisper what's askew today,
Tests stand guard, keep fakes at bay. 🐇

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning The PR description covers motivation, changes, and testing, but it does not address the required env-var checklist from the template. Add the checklist answers for env vars/secrets/NEXT_PUBLIC values, or explicitly mark them not applicable if none were changed.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly matches the main change: restoring the live Pandora dashboard.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/upgrade-pandora-dashboard-to-production-quality

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@besfeng23 besfeng23 merged commit 06ca7ca into main Jul 3, 2026
4 of 5 checks passed

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 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>;

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 👍 / 👎.

<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 👍 / 👎.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (5)
components/pandora/mock-data.ts (1)

6-8: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Minor duplication between mockStats and fixtureDashboardData.stats.

The single stat object in fixtureDashboardData.stats (Line 19) re-literalizes the same values as mockStats (Lines 6-8), just omitting icon. Since PandoraDashboardData.stats is typed as DashboardStatData[] (no icon required), you could derive it from mockStats instead 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 win

Mock-data guard doesn't cover the other converted card components.

productionFiles only checks app/pandora/page.tsx and PandoraDashboard.tsx, but this cohort also converts AdaptiveProfileCard, AskPandoraHero, DiagnosticsCard, MemorySpacesCard, RecentEventsTimeline, and WorkQueueCard off mock rendering. A regression reintroducing a mock-data import 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 win

Overbroad searchParams ban makes Line 14 unreachable and risks future false failures.

Line 13 fails the test on any occurrence of searchParams in page.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 if searchParams is 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 value

Consider 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/react with render/screen would 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 win

Icon assignment is positionally coupled to dashboardData.stats order.

statIcons[index] ties each icon to the stat array's index rather than its id. If the service ever reorders/add/removes a stat (e.g., lib/services/pandora-dashboard-service.ts's stats array), 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 LucideIcon type from lucide-react for 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

📥 Commits

Reviewing files that changed from the base of the PR and between aa8b59a and 3015527.

📒 Files selected for processing (12)
  • components/pandora/AdaptiveProfileCard.tsx
  • components/pandora/AskPandoraHero.tsx
  • components/pandora/DiagnosticsCard.tsx
  • components/pandora/MemorySpacesCard.tsx
  • components/pandora/PandoraDashboard.tsx
  • components/pandora/RecentEventsTimeline.tsx
  • components/pandora/WorkQueueCard.tsx
  • components/pandora/mock-data.ts
  • lib/services/pandora-dashboard-service.ts
  • tests/unit/pandora-dashboard-guard.test.ts
  • tests/unit/pandora-dashboard-service.test.ts
  • tests/unit/pandora-dashboard-ui.test.tsx

Comment on lines 39 to 48
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),
})));

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🚀 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.

Suggested change
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" })),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 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.

Suggested change
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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant