- PRD: product identity, architecture (BFF + web), data model, 10 pages, 21 widgets, cross-product feed/search, security, resilience, telemetry - Roadmap: 8 phases (scaffold, auth, feed, widgets, search, billing, security, polish), dependency graph, ~21 days estimated - Builds on CROSS_PRODUCT_USER_DASHBOARD.md design + DASHBOARD_UI_GAP_ANALYSIS.md findings
27 KiB
Unified ByteLyst Portal — Product Requirements Document
Date: 2026-03-21 · Status: Draft · Author: AI-assisted
Depends on: SmartAuth OneAuth (Phase 5),@bytelyst/dashboard-shell, platform-service
See also:CROSS_PRODUCT_USER_DASHBOARD.md,DASHBOARD_UI_GAP_ANALYSIS.md
1. Product Identity
| Key | Value |
|---|---|
| Product | ByteLyst Portal |
| Product ID | portal |
| Domain | portal.bytelyst.com |
| Repo | learning_ai_portal (new) |
| Ecosystem | ByteLyst (consumes platform-service + all 11 product backends) |
| Port (web) | 3010 |
| Port (BFF) | 4020 |
2. Problem Statement
The ByteLyst ecosystem has grown to 11 products (LysnrAI, MindLyst, ChronoMind, JarvisJr, NomGap, PeakPulse, FlowMonk, NoteLett, ActionTrail, LocalMemGPT, ByteLyst Auth), each with its own web dashboard, login, and settings. Users who use multiple products face:
- Separate logins — even though OneAuth shares identity, users still open different URLs per product
- No unified view — no way to see "what's happening across all my ByteLyst apps"
- Fragmented settings — notification preferences, theme, timezone set per-product
- No cross-product search — can't search across notes, timers, sessions, tasks from one place
- No unified billing — subscriptions managed per-product with no combined view
Meanwhile, the admin dashboard has grown to 43+ backend modules with 511+ endpoints, but the gap analysis shows 55 features are hidden or underexposed in the UI. The admin console is becoming unmanageable — it needs to evolve alongside the user-facing portal.
3. Goals
3.1 User-Facing Portal Goals
| # | Goal | Success Metric |
|---|---|---|
| G1 | Single login, all products — one URL, one session, access everything | 100% of products accessible from portal |
| G2 | Cross-product activity feed — timeline of recent actions across all products | Feed renders within 2s for users with 5+ products |
| G3 | Unified search — search notes, timers, tasks, sessions, memories from one bar | Cross-product search returns results in <1s |
| G4 | Product launcher — app-switcher grid (like Google's waffle menu) | All 11 products launchable with deep links |
| G5 | Combined billing — single view of all subscriptions, invoices, usage | Zero billing queries to support about "which product charges what" |
| G6 | Global settings — theme, timezone, notification prefs apply cross-product | Settings changes propagate to all products within 30s |
| G7 | Cross-product widgets — composable dashboard with pinnable product widgets | Users can customize their portal home with ≥10 widget types |
3.2 Platform Goals
| # | Goal | Success Metric |
|---|---|---|
| P1 | Reusable shell — portal built on @bytelyst/dashboard-shell |
Shell package consumed without forking |
| P2 | BFF pattern — portal has its own Backend-For-Frontend that aggregates data from product backends | No direct browser→product-backend calls |
| P3 | Progressive rollout — portal works even if some product backends are down | Graceful degradation for unavailable products |
| P4 | Extensible product cards — each product registers a "portal card" config | New products auto-appear in portal via product.json |
3.3 Non-Goals (Explicit)
- Not replacing product dashboards — portal is a hub, not a replacement. Deep actions still happen in product UIs.
- Not an admin console — portal is user-facing. Admin features stay in admin-web.
- Not a mobile app — web-only for v1. Native portal is a future consideration.
- Not real-time collaboration — this is a personal dashboard, not a shared workspace.
4. Architecture
4.1 High-Level Architecture
┌─────────────────────────────────────────────────────────────┐
│ ByteLyst Portal (web) │
│ Next.js 16 · port 3010 │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────────┐ │
│ │ Product │ │ Activity │ │ Search │ │ Settings │ │
│ │ Launcher │ │ Feed │ │ Bar │ │ & Billing │ │
│ └────┬─────┘ └────┬─────┘ └────┬─────┘ └──────┬───────┘ │
│ │ │ │ │ │
│ └─────────────┴────────────┴───────────────┘ │
│ │ │
│ Portal BFF (Fastify 5 · port 4020) │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ /api/portal/products — product registry │ │
│ │ /api/portal/feed — aggregated activity │ │
│ │ /api/portal/search — cross-product search │ │
│ │ /api/portal/widgets — widget data aggregation │ │
│ │ /api/portal/preferences — global user preferences │ │
│ │ /api/portal/billing — combined billing summary │ │
│ └──────────────────────────────────────────────────────┘ │
│ │ │
└──────────────────────────┼──────────────────────────────────┘
│
┌──────────────────┼──────────────────────┐
▼ ▼ ▼
platform-service product backends product backends
(port 4003) (ports 4010-4019) (ports 4010-4019)
┌──────────┐ ┌───────────────┐ ┌───────────────┐
│ auth │ │ LysnrAI 4015 │ │ FlowMonk 4017 │
│ billing │ │ MindLyst 4014 │ │ NoteLett 4016 │
│ flags │ │ ChronoMind4011│ │ ActionTrail4018│
│ telemetry│ │ JarvisJr 4012 │ │ LocalMem 4019 │
│ usage │ │ NomGap 4013 │ │ PeakPulse 4010 │
└──────────┘ └───────────────┘ └───────────────┘
4.2 Data Model
4.2.1 UserDoc Enhancement (already in place via OneAuth)
interface UserDoc {
id: string;
email: string;
primaryProductId: string;
memberships: ProductMembership[]; // ← from SmartAuth Phase 5
globalPreferences?: GlobalPreferences; // ← NEW for portal
}
interface ProductMembership {
productId: string;
role: 'user' | 'admin';
plan: 'free' | 'pro' | 'enterprise';
firstAccessAt: string;
subscriptionId?: string;
licenseId?: string;
}
interface GlobalPreferences {
theme: 'light' | 'dark' | 'system';
timezone: string;
locale: string;
notifications: {
email: boolean;
push: boolean;
inApp: boolean;
digest: 'none' | 'daily' | 'weekly';
};
portalLayout: WidgetLayout[];
}
4.2.2 Portal Widget Config
interface WidgetLayout {
widgetId: string; // e.g. 'lysnrai:recent-transcripts'
productId: string;
position: { row: number; col: number; width: number; height: number };
config?: Record<string, unknown>;
}
interface PortalWidgetDefinition {
id: string;
productId: string;
name: string;
description: string;
icon: string;
sizes: ('small' | 'medium' | 'large')[];
dataEndpoint: string; // BFF endpoint that fetches widget data
}
4.2.3 Product Registry (from existing product.json files)
Each product already has a product.json with:
id,name,displayName,domain,descriptiontheme(colors)features(capability flags)ports(service + dashboard)
The BFF reads all products/*/product.json at startup to build the product registry. No new schema needed.
4.3 BFF API Endpoints
| Method | Path | Description | Source |
|---|---|---|---|
| GET | /api/portal/products |
List products user has access to | platform-service /auth/me |
| GET | /api/portal/feed |
Cross-product activity feed (last 7d) | Fan-out to product backends |
| GET | /api/portal/search?q=... |
Cross-product search | Fan-out to product search endpoints |
| GET | /api/portal/widgets/:widgetId |
Fetch data for a specific widget | Product backend endpoint |
| GET | /api/portal/preferences |
Get global preferences | platform-service |
| PATCH | /api/portal/preferences |
Update global preferences | platform-service |
| GET | /api/portal/billing/summary |
Combined billing across products | platform-service subscriptions |
| GET | /api/portal/billing/invoices |
All invoices across products | platform-service stripe |
| POST | /api/portal/products/:id/switch |
Switch active product context | JWT re-issue |
| GET | /api/portal/notifications |
Aggregated notifications | platform-service |
| GET | /api/portal/quick-actions |
Available quick actions per product | Product registrations |
4.4 JWT Enhancement
Current JWT (already includes memberships via OneAuth):
{
"sub": "user-123",
"productId": "lysnrai",
"primaryProductId": "lysnrai",
"memberships": [
{ "productId": "lysnrai", "role": "user" },
{ "productId": "mindlyst", "role": "admin" }
]
}
Portal JWT adds a scope: "portal" claim so product backends know the request comes from the portal BFF:
{
"sub": "user-123",
"scope": "portal",
"primaryProductId": "lysnrai",
"memberships": [...]
}
4.5 Tech Stack
| Layer | Technology |
|---|---|
| Web | Next.js 16 (App Router), React 19, TailwindCSS v4, @bytelyst/dashboard-shell, Zustand, Recharts, Lucide icons |
| BFF | Fastify 5, TypeScript ESM, @bytelyst/fastify-core, @bytelyst/api-client, @bytelyst/auth |
| Auth | JWT via jose, cookie-based session, @bytelyst/react-auth |
| Shared packages | @bytelyst/config, @bytelyst/errors, @bytelyst/logger, @bytelyst/design-tokens, @bytelyst/telemetry-client, @bytelyst/feature-flag-client, @bytelyst/kill-switch-client |
| Database | Azure Cosmos DB (productId: "portal") for portal-specific data (widget layouts, preferences) |
| Tests | Vitest (BFF), Playwright (E2E) |
5. Pages & UI
5.1 Page Map
| Page | Route | Description |
|---|---|---|
| Home / Dashboard | / |
Customizable widget grid + activity feed |
| Products | /products |
Product launcher grid with status badges |
| Activity | /activity |
Full cross-product activity timeline with filters |
| Search | /search |
Cross-product search results grouped by product |
| Billing | /billing |
Combined subscriptions, invoices, usage |
| Settings | /settings |
Global preferences (theme, timezone, notifications) |
| Security | /security |
MFA, passkeys, devices, sessions (from SmartAuth) |
| Profile | /profile |
User profile, connected accounts (OAuth link/unlink) |
| Product Detail | /products/[id] |
Deep link into product with embedded iframe or redirect |
| Support | /support |
Cross-product support ticket creation and tracking |
5.2 Product Launcher (Home)
The home page has three zones:
-
Product Grid (top) — 3×4 grid of product cards. Each card shows:
- Product icon + name
- Status badge (active/trial/free)
- Last activity timestamp
- Quick action button (e.g., "New Timer" for ChronoMind, "Dictate" for LysnrAI)
- Click → opens product dashboard in new tab or embedded view
-
Activity Feed (left 60%) — chronological feed of recent events:
- "You completed a 25-min Pomodoro in ChronoMind"
- "New note created in NoteLett: 'Meeting Notes'"
- "JarvisJr coaching session: 3 insights extracted"
- Each item has product icon, timestamp, and deep link
-
Widgets (right 40%) — user-configurable widget stack:
- ChronoMind: next timer countdown
- NomGap: current fast progress
- FlowMonk: today's schedule
- MindLyst: brain activity heatmap
- LysnrAI: recent transcripts
- PeakPulse: weekly stats
5.3 Cross-Product Search
Global search bar in the top bar (Cmd+K). Results grouped by product:
🔍 "meeting notes"
📝 NoteLett (3 results)
- Meeting Notes — Project Alpha (2h ago)
- Weekly Standup Notes (yesterday)
- Client Meeting Follow-up (3d ago)
🎤 LysnrAI (2 results)
- Meeting transcript — 03/21 (today)
- Meeting transcript — 03/18
🧠 MindLyst (1 result)
- Memory: "Meeting with design team" in Work brain
5.4 Widget System
Widgets are the portal's core differentiator. Each product registers widget definitions:
| Widget ID | Product | Size | Description |
|---|---|---|---|
lysnrai:recent-transcripts |
LysnrAI | Medium | Last 5 transcripts with duration |
lysnrai:usage-chart |
LysnrAI | Large | Weekly transcription minutes chart |
chronomind:next-timer |
ChronoMind | Small | Countdown to next timer/alarm |
chronomind:today-schedule |
ChronoMind | Medium | Today's timers and routines |
nomgap:fast-progress |
NomGap | Small | Current fast elapsed/remaining |
nomgap:streak |
NomGap | Small | Fasting streak count |
mindlyst:brain-activity |
MindLyst | Medium | Brain captures heatmap (7d) |
mindlyst:recent-captures |
MindLyst | Medium | Last 5 captures with brain tags |
jarvisjr:coaching-streak |
JarvisJr | Small | Coaching session streak |
jarvisjr:recent-sessions |
JarvisJr | Medium | Last 3 agent sessions |
flowmonk:today-schedule |
FlowMonk | Medium | Today's planned flow slots |
flowmonk:task-summary |
FlowMonk | Small | Tasks: pending/done/overdue |
notelett:recent-notes |
NoteLett | Medium | Last 5 notes with tags |
notelett:workspace-summary |
NoteLett | Small | Workspace note counts |
actiontrail:recent-actions |
ActionTrail | Medium | Last 10 agent actions |
actiontrail:risk-summary |
ActionTrail | Small | Risk level distribution |
peakpulse:weekly-stats |
PeakPulse | Medium | Distance, elevation, sessions |
peakpulse:recent-sessions |
PeakPulse | Medium | Last 3 adventure sessions |
localmemgpt:recent-chats |
LocalMemGPT | Medium | Last 5 conversations |
portal:billing-summary |
Portal | Small | Combined monthly cost |
portal:security-status |
Portal | Small | MFA status, device count |
6. Cross-Product Activity Feed
6.1 Feed Event Schema
interface FeedEvent {
id: string;
productId: string;
productName: string;
productIcon: string;
userId: string;
type: string; // product-specific event type
title: string; // human-readable summary
description?: string;
deepLink?: string; // URL to the item in the product dashboard
metadata?: Record<string, unknown>;
createdAt: string;
}
6.2 Event Sources per Product
| Product | Event Types | Source |
|---|---|---|
| LysnrAI | transcript.created, session.completed |
GET /api/transcripts?limit=10 |
| MindLyst | capture.created, triage.completed |
GET /api/memory-items?limit=10 |
| ChronoMind | timer.completed, routine.finished, pomodoro.done |
GET /api/timers/recent |
| JarvisJr | session.completed, memory.created |
GET /api/jarvis-sessions?limit=10 |
| NomGap | fast.started, fast.completed, milestone.reached |
GET /api/fasting-sessions?limit=10 |
| PeakPulse | session.recorded, goal.achieved, badge.unlocked |
GET /api/sessions?limit=10 |
| FlowMonk | task.completed, schedule.generated, entry.done |
GET /api/schedule-entries?limit=10 |
| NoteLett | note.created, note.updated, task.extracted |
GET /api/notes?limit=10 |
| ActionTrail | action.ingested, alert.fired, approval.decided |
GET /api/actions?limit=10 |
| LocalMemGPT | conversation.created, document.uploaded |
GET /api/conversations?limit=10 |
6.3 Aggregation Strategy
The BFF fetches from all product backends in parallel with:
- Timeout: 3s per product (fail-open — missing products show "temporarily unavailable")
- Caching: 60s TTL in-memory cache per user
- Pagination: Feed returns 50 items max, sorted by
createdAtdesc - Circuit breaker: After 3 consecutive failures, skip product for 5 minutes
7. Security & Auth
7.1 Authentication Flow
- User visits
portal.bytelyst.com - If no session, redirect to
/login(uses platform-service/auth/login) - On success, BFF issues a portal-scoped JWT with all memberships
- JWT stored as httpOnly cookie (not localStorage)
- BFF proxies requests to product backends using service-to-service auth
7.2 Authorization
- Portal BFF has a service API key for each product backend
- When fetching product data, BFF sends
x-user-id+x-portal-tokenheaders - Product backends validate the portal service key and scope data to the user
- Users can only see data for products they have memberships in
7.3 Security Page Features
Leverages existing SmartAuth infrastructure:
- MFA management — TOTP setup/disable, recovery codes
- Passkey management — register/remove WebAuthn credentials
- Device management — view trusted devices, revoke sessions
- Login history — recent login events with IP/location
- Connected accounts — link/unlink Google, Microsoft, Apple OAuth
8. Offline & Resilience
8.1 Graceful Degradation
The portal must work even when some product backends are down:
| Scenario | Behavior |
|---|---|
| Product backend down | Widget shows "Temporarily unavailable" with retry button |
| Platform-service down | Portal shows cached data, auth redirects to maintenance page |
| BFF down | Static Next.js pages with "Service connecting..." message |
| Slow product backend (>3s) | Widget shows skeleton, loads async when available |
8.2 Caching Strategy
| Data | Cache TTL | Storage |
|---|---|---|
| Product registry | 1 hour | BFF memory |
| User memberships | 5 minutes | BFF memory |
| Activity feed | 60 seconds | BFF memory per user |
| Widget data | 30 seconds | BFF memory per widget |
| Global preferences | 10 minutes | BFF memory per user |
| Search results | No cache | Real-time fan-out |
9. Telemetry & Analytics
9.1 Portal-Specific Events
| Event | Properties |
|---|---|
portal.product_launched |
productId, source (grid/feed/search/widget) |
portal.search_executed |
query, resultCount, productHits[] |
portal.widget_interacted |
widgetId, action |
portal.layout_customized |
widgetCount, widgetIds[] |
portal.preference_changed |
key, oldValue, newValue |
portal.feed_scrolled |
depth, productsVisible[] |
9.2 Cross-Product Insights (Admin)
The portal BFF can provide admin-web with:
- Cross-product adoption: How many users use 1, 2, 3+ products
- Product affinity: Which products are commonly used together
- Portal engagement: Time spent in portal vs. direct product access
- Widget popularity: Most/least configured widgets
10. Open Questions
| # | Question | Options | Recommendation |
|---|---|---|---|
| Q1 | Standalone repo or inside common-plat? | Standalone learning_ai_portal vs. dashboards/portal-web/ |
Standalone repo — it has its own BFF, making it more than a dashboard |
| Q2 | Embedded product views or redirect? | iframe embed vs. open in new tab | New tab for v1, embedded for v2 |
| Q3 | Should BFF own its own Cosmos containers? | Yes (portal-specific) vs. use platform-service only | Yes — for widget layouts, portal preferences, feed cache |
| Q4 | Widget data: pull or push? | BFF polls products vs. products push events | Pull for v1 (simpler), SSE push for v2 |
| Q5 | How does portal handle products user hasn't signed up for? | Hide vs. show with "Get Started" CTA | Show with CTA — drives cross-product adoption |
11. Dependencies
| Dependency | Status | Blocker? |
|---|---|---|
OneAuth memberships[] on UserDoc |
✅ Implemented | No |
/auth/me returns product list |
✅ Implemented | No |
@bytelyst/dashboard-shell |
✅ Published | No |
@bytelyst/react-auth |
✅ Published | No |
| Product backends expose recent-items APIs | ⚠️ Partial — most have list endpoints | Non-blocking (graceful degradation) |
product.json files for all 11 products |
✅ 7 exist, 4 need creation | Non-blocking |
| Service-to-service auth (portal BFF → product backends) | ❌ Needs implementation | Phase 1 blocker |
| Global preferences on UserDoc | ❌ Needs implementation | Phase 2 blocker |
This PRD should be reviewed and updated as implementation progresses.