Review Errata (15 findings): - F1-F2: Product count 11→10 (ByteLyst Auth has no backend) - F3: product.json count 4→3 missing (FlowMonk, ActionTrail, LocalMemGPT) - F4: Architecture diagram spacing fixes - F5: Remove portalLayout from GlobalPreferences (contradicted separate Cosmos container) - F6-F7: Widget endpoint unified to /widgets/:widgetId/data + added layout CRUD endpoints - F8: Verified all 10 feed source endpoints against actual backend route files - F9: Added @bytelyst/datastore to BFF tech stack - F10: Added Cosmos containers section (portal_widget_layouts) - F11: Global prefs dependency Phase 2→Phase 5 blocker - F12: Removed emojis from search example - F13: Added quick-actions task to roadmap Phase 1 - F14: Added 4 missing scaffold tasks (errors, datastore, cosmos-init, vitest) - F15: Corrected run-local-all-services.sh location - Updated task totals: 124→129
34 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
Review Errata (2026-03-21 self-audit)
The following bugs/inaccuracies were found during systematic post-generation review and corrected in-place:
| # | Bug | Severity | Fix Applied |
|---|---|---|---|
| F1 | Product count wrong: Said "11 products" but ByteLyst Auth has no backend -- only 10 products have backends the portal can query | HIGH | Changed to "10 products with backends" + noted Auth as companion app |
| F2 | ByteLyst Auth listed as product with backend -- it's a companion app consuming platform-service, no data to aggregate | HIGH | Removed from product count, clarified in problem statement |
| F3 | product.json count wrong: Said "7 exist, 4 need creation" but only 3 are missing (FlowMonk, ActionTrail, LocalMemGPT) | MEDIUM | Corrected to "7 exist, 3 need creation" with specific names |
| F4 | Architecture diagram spacing: "ChronoMind4011" and "ActionTrail4018" missing spaces | LOW | Fixed spacing |
| F5 | GlobalPreferences contained portalLayout: Widget layouts were inside GlobalPreferences AND in a separate Cosmos container -- contradictory | MEDIUM | Removed portalLayout from GlobalPreferences; layouts stored in portal_widget_layouts container only |
| F6 | Widget endpoint mismatch: PRD API table said /widgets/:widgetId, roadmap said /widgets/:widgetId/data |
MEDIUM | Unified to /widgets/:widgetId/data |
| F7 | Missing widget layout endpoints in API table: GET/PUT for layout management were in roadmap but not documented in PRD | MEDIUM | Added GET/PUT /api/portal/widgets/layout + GET /api/portal/widgets/definitions to API table |
| F8 | Feed event source endpoints unverified: Several assumed endpoints were wrong (ChronoMind /timers/recent, JarvisJr /jarvis-sessions, NomGap /fasting-sessions, PeakPulse /sessions) |
HIGH | Verified all 10 endpoints against actual backend route files; corrected paths |
| F9 | Missing @bytelyst/datastore in BFF tech stack -- required for Cosmos container access |
MEDIUM | Added to BFF tech stack row |
| F10 | No Cosmos containers section -- widget layouts need explicit container documentation | MEDIUM | Added section 4.6 with portal_widget_layouts container |
| F11 | Dependency table: global prefs marked "Phase 2 blocker" -- actually used in Phase 5 (Settings) | LOW | Changed to "Phase 5 blocker" |
| F12 | Search example used emojis -- violates "no emojis unless explicitly asked" rule | LOW | Removed emojis from search example |
| F13 | Missing quick-actions in roadmap -- PRD listed endpoint but no roadmap task built it | LOW | Added task 1.13 to roadmap Phase 1 |
| F14 | Roadmap missing standard BFF scaffold files -- lib/errors.ts, lib/datastore.ts, lib/cosmos-init.ts, vitest.config.ts not listed |
MEDIUM | Added 4 tasks (0.11-0.14) to Phase 0 |
| F15 | Roadmap task 7.19 wrong script location -- run-local-all-services.sh is in learning_voice_ai_agent/, not common-plat |
LOW | Corrected with "(in learning_voice_ai_agent repo)" note |
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 10 product backends) |
| Port (web) | 3010 |
| Port (BFF) | 4020 |
2. Problem Statement
The ByteLyst ecosystem has grown to 10 products with backends (LysnrAI, MindLyst, ChronoMind, JarvisJr, NomGap, PeakPulse, FlowMonk, NoteLett, ActionTrail, LocalMemGPT) plus ByteLyst Auth (a companion app with no separate backend). Each product has 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 10 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 10 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 │ │ ChronoMind 4011│ │ ActionTrail 4018│
│ 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';
};
// NOTE: Widget layouts are stored separately in Cosmos container
// `portal_widget_layouts` (partition: /userId), not here.
// This keeps GlobalPreferences lightweight on UserDoc.
}
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/definitions |
List available widget definitions | Static registry in BFF |
| GET | /api/portal/widgets/layout |
Get user's widget layout | Cosmos portal_widget_layouts |
| PUT | /api/portal/widgets/layout |
Save user's widget layout | Cosmos portal_widget_layouts |
| GET | /api/portal/widgets/:widgetId/data |
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, @bytelyst/datastore |
| 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) |
4.6 Cosmos Containers
Portal-specific data stored in Azure Cosmos DB with productId: "portal":
| Container | Partition Key | Purpose |
|---|---|---|
portal_widget_layouts |
/userId |
User widget grid configurations |
Global user preferences (GlobalPreferences) are stored on the existing UserDoc in platform-service's users container -- no new container needed for preferences.
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 | Verified Backend Endpoint |
|---|---|---|
| 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?limit=10&sort=updatedAt |
| 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/peak/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 (no limit param) |
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 10 products |
✅ 7 exist, 3 need creation (FlowMonk, ActionTrail, LocalMemGPT) | Non-blocking |
| Service-to-service auth (portal BFF → product backends) | ❌ Needs implementation | Phase 1 blocker |
| Global preferences on UserDoc | ❌ Needs implementation | Phase 5 blocker |
This PRD should be reviewed and updated as implementation progresses.