From 7a4cca034c6fd61e2d54347d80582d03333244b4 Mon Sep 17 00:00:00 2001 From: saravanakumardb1 Date: Sat, 21 Mar 2026 20:49:55 -0700 Subject: [PATCH] docs: add Unified ByteLyst Portal PRD + phased roadmap with 124 tasks - 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 --- docs/UNIFIED_BYTELYST_PORTAL_PRD.md | 477 ++++++++++++++++++++++++ docs/UNIFIED_BYTELYST_PORTAL_ROADMAP.md | 275 ++++++++++++++ 2 files changed, 752 insertions(+) create mode 100644 docs/UNIFIED_BYTELYST_PORTAL_PRD.md create mode 100644 docs/UNIFIED_BYTELYST_PORTAL_ROADMAP.md diff --git a/docs/UNIFIED_BYTELYST_PORTAL_PRD.md b/docs/UNIFIED_BYTELYST_PORTAL_PRD.md new file mode 100644 index 00000000..c64ea50b --- /dev/null +++ b/docs/UNIFIED_BYTELYST_PORTAL_PRD.md @@ -0,0 +1,477 @@ +# 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`](design/CROSS_PRODUCT_USER_DASHBOARD.md), [`DASHBOARD_UI_GAP_ANALYSIS.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: + +1. **Separate logins** — even though OneAuth shares identity, users still open different URLs per product +2. **No unified view** — no way to see "what's happening across all my ByteLyst apps" +3. **Fragmented settings** — notification preferences, theme, timezone set per-product +4. **No cross-product search** — can't search across notes, timers, sessions, tasks from one place +5. **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) + +```typescript +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 + +```typescript +interface WidgetLayout { + widgetId: string; // e.g. 'lysnrai:recent-transcripts' + productId: string; + position: { row: number; col: number; width: number; height: number }; + config?: Record; +} + +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`, `description` +- `theme` (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): + +```json +{ + "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: + +```json +{ + "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: + +1. **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 + +2. **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 + +3. **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 + +```typescript +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; + 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 `createdAt` desc +- **Circuit breaker:** After 3 consecutive failures, skip product for 5 minutes + +--- + +## 7. Security & Auth + +### 7.1 Authentication Flow + +1. User visits `portal.bytelyst.com` +2. If no session, redirect to `/login` (uses platform-service `/auth/login`) +3. On success, BFF issues a portal-scoped JWT with all memberships +4. JWT stored as httpOnly cookie (not localStorage) +5. 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-token` headers +- 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._ diff --git a/docs/UNIFIED_BYTELYST_PORTAL_ROADMAP.md b/docs/UNIFIED_BYTELYST_PORTAL_ROADMAP.md new file mode 100644 index 00000000..79eb1112 --- /dev/null +++ b/docs/UNIFIED_BYTELYST_PORTAL_ROADMAP.md @@ -0,0 +1,275 @@ +# Unified ByteLyst Portal — Roadmap & Task List + +> **Date:** 2026-03-21 · **Companion to:** [`UNIFIED_BYTELYST_PORTAL_PRD.md`](UNIFIED_BYTELYST_PORTAL_PRD.md) +> **Estimated total:** 8 phases · ~124 tasks · 6-8 weeks + +--- + +## Phase 0: Repo Scaffold & Foundations (2 days) + +> **Goal:** Repo exists, builds, deploys to dev. BFF starts and returns health check. Web renders shell. + +| # | Task | Type | Est | Notes | +| ---- | ----------------------------------------------------------------------------------------- | ------- | --- | --------------------------------------------------------- | +| 0.1 | Create `learning_ai_portal` repo with standard structure | infra | 1h | `backend/`, `web/`, `shared/`, `docs/`, `AGENTS.md` | +| 0.2 | Create `shared/product.json` (id: `portal`, port: 4020/3010) | config | 15m | Follow existing product.json pattern | +| 0.3 | Create `products/portal/product.json` in common-plat | config | 15m | Register portal in product registry | +| 0.4 | Scaffold BFF with `@bytelyst/fastify-core` (port 4020) | backend | 1h | `createServiceApp()` + health endpoint | +| 0.5 | Add `lib/config.ts` -- Zod-validated env (JWT_SECRET, PLATFORM_URL, product backend URLs) | backend | 30m | | +| 0.6 | Add `lib/product-config.ts` -- load from `shared/product.json` | backend | 15m | | +| 0.7 | Add `lib/auth.ts` -- JWT extraction + portal scope validation | backend | 30m | | +| 0.8 | Add `lib/request-context.ts` -- `getUserId(req)`, `getUserMemberships(req)` | backend | 30m | | +| 0.9 | Add `lib/product-registry.ts` -- load all product.json files, build registry | backend | 1h | Read from `../../products/*/product.json` or env-injected | +| 0.10 | Add `lib/product-client.ts` -- service-to-service HTTP client for product backends | backend | 1h | Timeout, circuit breaker, x-user-id header | +| 0.11 | Scaffold web with Next.js 16 + `@bytelyst/dashboard-shell` (port 3010) | web | 1h | App Router, TailwindCSS v4, `--portal-*` CSS props | +| 0.12 | Add `web/src/lib/product-config.ts` -- product identity + API URLs | web | 15m | | +| 0.13 | Add `web/src/lib/auth.ts` -- `@bytelyst/react-auth` provider | web | 30m | Cookie-based auth | +| 0.14 | Add `web/src/lib/api.ts` -- typed fetch client for BFF | web | 30m | | +| 0.15 | Add `web/src/app/layout.tsx` + `providers.tsx` -- shell + auth + telemetry | web | 30m | | +| 0.16 | Create `AGENTS.md` with project conventions | docs | 30m | | +| 0.17 | Create `.github/workflows/ci.yml` -- typecheck + test for backend + web | infra | 30m | | +| 0.18 | Verify: `cd backend && npm test && npm run typecheck` passes | test | 15m | | +| 0.19 | Verify: `cd web && npm run typecheck && npm run build` passes | test | 15m | | + +**Phase 0 deliverable:** Repo builds, BFF returns `GET /health`, web renders empty shell with sidebar. + +--- + +## Phase 1: Product Launcher & Auth (3 days) + +> **Goal:** User can log in, see all their products, and launch any product dashboard. + +| # | Task | Type | Est | Notes | +| ---- | ----------------------------------------------------------------------------------------------------------------------------- | ------- | --- | ------------------------------------------------------------------------------------ | +| 1.1 | BFF: `GET /api/portal/products` -- fetch user memberships from platform-service `/auth/me`, enrich with product.json metadata | backend | 2h | Returns `{ products: [{ id, name, icon, plan, role, dashboardUrl, lastActiveAt }] }` | +| 1.2 | BFF: `POST /api/portal/products/:id/switch` -- re-issue JWT with new active productId | backend | 1h | | +| 1.3 | BFF: service-to-service auth setup -- API key registration for portal-to-product-backend calls | backend | 2h | Register `portal` as a service consumer in platform-service | +| 1.4 | Web: `/login` page -- email/password + OAuth buttons via `@bytelyst/react-auth` | web | 2h | Redirect to `/` on success | +| 1.5 | Web: `(app)/layout.tsx` -- authenticated route group with `DashboardShell` | web | 1h | Sidebar nav: Home, Products, Activity, Search, Billing, Settings, Security | +| 1.6 | Web: `Sidebar.tsx` -- portal sidebar with product icons + nav sections | web | 1h | | +| 1.7 | Web: `/products` page -- product launcher grid | web | 3h | Card per product: icon, name, plan badge, last active, "Open" button | +| 1.8 | Web: `ProductCard.tsx` component -- product card with theme colors from product.json | web | 1h | | +| 1.9 | Web: `/` redirect to `/products` (temporary, until dashboard is built in Phase 3) | web | 15m | | +| 1.10 | Web: product launch handler -- opens product dashboard in new tab with session context | web | 1h | Deep link: `https://{domain}/?portal=true` | +| 1.11 | Web: show "Get Started" CTA for products user hasn't signed up for | web | 1h | Links to product landing page | +| 1.12 | Test: BFF products endpoint returns correct memberships | test | 1h | Vitest | +| 1.13 | Test: Login flow to product grid to launch works E2E | test | 1h | Playwright | + +**Phase 1 deliverable:** User logs in, sees grid of their products with status, can open any product in a new tab. + +--- + +## Phase 2: Activity Feed & Notifications (3 days) + +> **Goal:** User sees a chronological feed of recent actions across all products. + +| # | Task | Type | Est | Notes | +| ---- | ---------------------------------------------------------------------------------------------------- | ------- | --- | -------------------------------------------------------------- | +| 2.1 | BFF: `lib/feed-aggregator.ts` -- parallel fan-out to product backends with timeout + circuit breaker | backend | 3h | 3s timeout per product, fail-open | +| 2.2 | BFF: Define `FeedEvent` schema + product-specific event mappers | backend | 2h | One mapper per product that normalizes response into FeedEvent | +| 2.3 | BFF: `GET /api/portal/feed` -- aggregated feed with pagination (`?cursor=&limit=50`) | backend | 2h | Merge-sort by createdAt desc | +| 2.4 | BFF: In-memory cache (60s TTL per user) for feed data | backend | 1h | | +| 2.5 | BFF: `GET /api/portal/notifications` -- aggregate from platform-service notifications | backend | 1h | | +| 2.6 | Web: `/activity` page -- full-page activity timeline | web | 3h | Filter by product, date range, event type | +| 2.7 | Web: `FeedCard.tsx` -- event card with product icon, title, timestamp, deep link | web | 1h | | +| 2.8 | Web: `FeedFilters.tsx` -- product multi-select, date range picker | web | 1h | | +| 2.9 | Web: Activity feed widget on `/` home page (compact view, last 10 items) | web | 1h | | +| 2.10 | Web: Notification bell in top bar -- badge count + dropdown | web | 2h | | +| 2.11 | Product backends: Verify each backend has a "recent items" list endpoint | backend | 2h | Add missing endpoints where needed | +| 2.12 | Test: Feed aggregation with 3 products up, 2 down | test | 1h | Circuit breaker test | +| 2.13 | Test: Feed pagination, sorting, filtering | test | 1h | | + +**Phase 2 deliverable:** `/activity` page shows chronological feed from all products. Home page shows compact feed. + +--- + +## Phase 3: Widget Dashboard (4 days) + +> **Goal:** Home page is a customizable widget grid. Users can add/remove/resize widgets. + +| # | Task | Type | Est | Notes | +| ---- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | --- | --------------------------------------------------------- | +| 3.1 | BFF: `modules/widgets/types.ts` -- WidgetDefinition, WidgetLayout schemas | backend | 1h | | +| 3.2 | BFF: `modules/widgets/repository.ts` -- CRUD for user widget layouts in Cosmos | backend | 2h | Container: `portal_widget_layouts`, partition: `/userId` | +| 3.3 | BFF: `modules/widgets/routes.ts` -- GET/PUT layout, GET widget definitions | backend | 2h | | +| 3.4 | BFF: `GET /api/portal/widgets/:widgetId/data` -- fetch widget data from product backend | backend | 2h | Route to correct product backend based on widgetId prefix | +| 3.5 | BFF: Register 20+ widget definitions (from PRD section 5.4) | backend | 2h | Static definitions loaded at startup | +| 3.6 | BFF: Cosmos container registration for `portal_widget_layouts` | backend | 30m | | +| 3.7 | Web: `/` home page with widget grid layout (CSS Grid) | web | 3h | Fixed grid for v1, drag-and-drop in Phase 8 | +| 3.8 | Web: `WidgetShell.tsx` -- generic widget wrapper (header, loading, error, resize) | web | 2h | | +| 3.9 | Web: `WidgetPicker.tsx` -- modal to add widgets from catalog | web | 2h | Grouped by product | +| 3.10 | Web: Implement 5 priority widgets: ChronoMind next-timer, NomGap fast-progress, FlowMonk today-schedule, NoteLett recent-notes, Portal billing-summary | web | 4h | ~45m each | +| 3.11 | Web: Implement 5 more widgets: LysnrAI recent-transcripts, MindLyst recent-captures, JarvisJr coaching-streak, ActionTrail recent-actions, PeakPulse weekly-stats | web | 4h | | +| 3.12 | Web: Implement remaining 11 widgets | web | 5h | | +| 3.13 | Web: `useWidgetData(widgetId)` hook -- fetch + cache + loading/error states | web | 1h | | +| 3.14 | Web: Default widget layout for new users (6 widgets: feed + 5 most-active products) | web | 1h | | +| 3.15 | Web: Save layout changes to BFF via PUT /api/portal/widgets/layout | web | 1h | Debounced save | +| 3.16 | Test: Widget CRUD, layout persistence | test | 1h | | +| 3.17 | Test: Widget data fetch with product backend down (graceful degradation) | test | 1h | | + +**Phase 3 deliverable:** Home page has customizable widget grid. Each widget fetches live data. + +--- + +## Phase 4: Cross-Product Search (2 days) + +> **Goal:** Cmd+K opens a global search bar that searches across all products. + +| # | Task | Type | Est | Notes | +| ---- | ------------------------------------------------------------------------------ | ------- | --- | --------------------------------------------------------------------------- | +| 4.1 | BFF: `lib/search-aggregator.ts` -- parallel search fan-out to product backends | backend | 2h | Same circuit-breaker pattern as feed | +| 4.2 | BFF: `GET /api/portal/search?q=...&products=...` -- cross-product search | backend | 2h | Returns results grouped by product | +| 4.3 | BFF: Product search endpoint mapping -- which endpoint to call per product | backend | 1h | e.g., NoteLett: `GET /api/notes?search=`, LocalMemGPT: `GET /api/search?q=` | +| 4.4 | Web: `/search` page -- full search results grouped by product | web | 3h | Product sections, result cards with deep links | +| 4.5 | Web: `SearchBar.tsx` -- Cmd+K global search overlay | web | 2h | Debounced input, recent searches, keyboard nav | +| 4.6 | Web: `SearchResultCard.tsx` -- generic result card with product badge | web | 1h | | +| 4.7 | Web: Add `use-keyboard-shortcuts.ts` -- Cmd+K to open search | web | 30m | | +| 4.8 | Product backends: Verify search/filter endpoints exist for each product | backend | 2h | Add missing search endpoints | +| 4.9 | Test: Search across 3 products, results merge correctly | test | 1h | | +| 4.10 | Test: Search with one product down, others still return | test | 30m | | + +**Phase 4 deliverable:** Cmd+K opens cross-product search. Results grouped by product with deep links. + +--- + +## Phase 5: Billing & Settings (2 days) + +> **Goal:** User manages billing and global preferences from the portal. + +| # | Task | Type | Est | Notes | +| ---- | ------------------------------------------------------------------------------------------------ | ------- | --- | -------------------------------------------- | +| 5.1 | BFF: `GET /api/portal/billing/summary` -- aggregate from platform-service subscriptions + stripe | backend | 2h | Combined monthly cost, per-product breakdown | +| 5.2 | BFF: `GET /api/portal/billing/invoices` -- all invoices across products | backend | 1h | | +| 5.3 | BFF: `GET /api/portal/preferences` -- user global preferences | backend | 1h | From UserDoc.globalPreferences | +| 5.4 | BFF: `PATCH /api/portal/preferences` -- update preferences | backend | 1h | Validate with Zod, save to platform-service | +| 5.5 | Platform-service: Add `globalPreferences` field to UserDoc schema | backend | 1h | Optional, backward-compatible | +| 5.6 | Platform-service: Add `PATCH /auth/preferences` endpoint | backend | 1h | | +| 5.7 | Web: `/billing` page -- subscription cards per product, combined total, invoice list | web | 3h | | +| 5.8 | Web: `/settings` page -- theme toggle, timezone picker, locale, notification prefs | web | 3h | | +| 5.9 | Web: Theme toggle (dark/light/system) applies to portal immediately | web | 1h | | +| 5.10 | Web: Notification preferences -- email/push/in-app toggles, digest frequency | web | 1h | | +| 5.11 | Test: Billing aggregation with mixed plan types | test | 1h | | +| 5.12 | Test: Preferences save and reload correctly | test | 1h | | + +**Phase 5 deliverable:** `/billing` shows combined costs. `/settings` manages cross-product preferences. + +--- + +## Phase 6: Security & Profile (2 days) + +> **Goal:** Full SmartAuth security management from the portal. + +| # | Task | Type | Est | Notes | +| ---- | ---------------------------------------------------------------------------------------- | ------- | --- | ------------------------------------------- | +| 6.1 | BFF: Proxy security endpoints to platform-service (MFA, passkeys, devices, login-events) | backend | 2h | Same catch-all proxy pattern | +| 6.2 | Web: `/security` page -- MFA status, TOTP setup/disable, recovery codes | web | 3h | Reuse patterns from admin-web security page | +| 6.3 | Web: `/security` -- passkey management (register, list, remove) | web | 2h | WebAuthn browser API integration | +| 6.4 | Web: `/security` -- device list with trust levels, session revocation | web | 2h | | +| 6.5 | Web: `/security` -- login history timeline | web | 1h | | +| 6.6 | Web: `/profile` page -- name, email, avatar, connected OAuth accounts | web | 2h | | +| 6.7 | Web: Connected accounts section -- link/unlink Google, Microsoft, Apple | web | 2h | | +| 6.8 | Web: `/support` page -- create/track support tickets via platform-service support-cases | web | 2h | | +| 6.9 | Test: MFA setup flow E2E | test | 1h | | +| 6.10 | Test: Device revocation works from portal | test | 30m | | + +**Phase 6 deliverable:** Full security management. Profile with OAuth link/unlink. Support ticket creation. + +--- + +## Phase 7: Telemetry, Polish & Launch (3 days) + +> **Goal:** Production-ready portal with telemetry, error handling, and polished UI. + +| # | Task | Type | Est | Notes | +| ---- | ----------------------------------------------------------------------------------------------- | ------- | --- | ----- | +| 7.1 | BFF: Telemetry event buffer -- portal-specific events (product_launched, search_executed, etc.) | backend | 1h | | +| 7.2 | BFF: Feature flags integration -- `@bytelyst/feature-flag-client` | backend | 30m | | +| 7.3 | BFF: Kill switch integration -- `@bytelyst/kill-switch-client` | backend | 30m | | +| 7.4 | BFF: Rate limiting on search and feed endpoints | backend | 1h | | +| 7.5 | BFF: Request logging with structured context | backend | 30m | | +| 7.6 | Web: Telemetry client -- track portal events via `@bytelyst/telemetry-client` | web | 1h | | +| 7.7 | Web: Error boundaries -- per-widget and per-page | web | 1h | | +| 7.8 | Web: Loading skeletons for all pages | web | 2h | | +| 7.9 | Web: Empty states for zero-product users (onboarding CTA) | web | 1h | | +| 7.10 | Web: Responsive design -- mobile-friendly sidebar collapse | web | 2h | | +| 7.11 | Web: PWA manifest + service worker for offline shell | web | 1h | | +| 7.12 | Web: `--portal-*` design tokens from `@bytelyst/design-tokens` | web | 1h | | +| 7.13 | Web: Keyboard shortcuts -- Cmd+K (search), Cmd+1-9 (product launch) | web | 1h | | +| 7.14 | Docs: Update `AGENTS.md` with final architecture | docs | 1h | | +| 7.15 | Docs: Write `README.md` with setup instructions | docs | 30m | | +| 7.16 | Docker: `Dockerfile` for BFF + web | infra | 1h | | +| 7.17 | Docker: Add to `docker-compose.yml` in common-plat | infra | 30m | | +| 7.18 | CI: Playwright E2E test suite (10 core flows) | test | 3h | | +| 7.19 | CI: Add portal to `run-local-all-services.sh` | infra | 15m | | +| 7.20 | Final: Typecheck + test + build green across BFF + web | test | 1h | | + +**Phase 7 deliverable:** Production-ready portal. All tests pass. Docker-deployable. Telemetry wired. + +--- + +## Phase 8: Post-Launch Enhancements (Ongoing) + +> **Goal:** Iterative improvements based on usage data. + +| # | Task | Type | Est | Notes | +| ---- | ------------------------------------------------------------- | ------- | --- | --------------------------------------------------- | +| 8.1 | Drag-and-drop widget reordering (react-dnd or dnd-kit) | web | 3h | | +| 8.2 | Embedded product views (iframe) with postMessage auth | web | 4h | v2 of product launcher | +| 8.3 | SSE-based real-time feed updates | backend | 3h | `@bytelyst/fastify-sse` | +| 8.4 | Cross-product quick actions (e.g., "Start Timer" from portal) | web+bff | 4h | Product backends expose action endpoints | +| 8.5 | Mobile-responsive PWA optimization | web | 2h | | +| 8.6 | Admin analytics -- portal engagement metrics in admin-web | web | 3h | Cross-product adoption charts | +| 8.7 | Smart widget suggestions based on user activity patterns | backend | 2h | "You use ChronoMind daily -- add the timer widget?" | +| 8.8 | Unified notification center with mark-read, filters | web | 3h | | +| 8.9 | Product health badges -- show backend status in product cards | bff | 1h | Poll health endpoints | +| 8.10 | Custom widget themes -- user sets accent color per widget | web | 1h | | + +--- + +## Summary + +| Phase | Name | Duration | Tasks | Key Deliverable | +| ----- | ------------------------------- | ------------ | ------- | ----------------------------------- | +| **0** | Repo Scaffold and Foundations | 2 days | 19 | Repo builds, shell renders | +| **1** | Product Launcher and Auth | 3 days | 13 | Login, product grid, launch | +| **2** | Activity Feed and Notifications | 3 days | 13 | Cross-product activity timeline | +| **3** | Widget Dashboard | 4 days | 17 | Customizable widget grid | +| **4** | Cross-Product Search | 2 days | 10 | Cmd+K global search | +| **5** | Billing and Settings | 2 days | 12 | Combined billing + global prefs | +| **6** | Security and Profile | 2 days | 10 | Full SmartAuth security from portal | +| **7** | Polish and Launch | 3 days | 20 | Production-ready, Docker, E2E | +| **8** | Post-Launch | Ongoing | 10 | Drag-drop, SSE, quick actions | +| | **Total** | **~21 days** | **124** | | + +--- + +## Dependency Graph + +``` +Phase 0 (scaffold) + | + v +Phase 1 (auth + products) + | + +---> Phase 2 (feed) ---> Phase 3 (widgets) + | + +---> Phase 4 (search) + | + +---> Phase 5 (billing + settings) + | + +---> Phase 6 (security + profile) + | + v +Phase 7 (polish + launch) <-- all above complete + | + v +Phase 8 (post-launch enhancements) +``` + +**Critical path:** Phase 0 -> Phase 1 -> Phase 2 -> Phase 3 -> Phase 7 + +Phases 4, 5, 6 can be parallelized after Phase 1. + +--- + +_This roadmap should be reviewed after each phase. Estimates are in ideal developer-hours._