docs: fix 17 bugs/gaps in shared client packages roadmap + add workflow
Review findings and fixes:
- Fix subscription routes use :userId not :id
- Fix GET /plans returns { plans: [...] } wrapper
- Fix org routes require admin-only JWT role
- Flag missing POST /referrals/apply endpoint
- Flag missing POST /subscriptions/restore endpoint
- Expand org-client and marketplace-client with full API signatures
- Add x-product-id header requirement
- Add NomGap Migration Plan
- Add Known Backend Gaps appendix
- Add /implement-shared-packages workflow
This commit is contained in:
parent
0d20987f75
commit
c87a8e9ef1
298
.windsurf/workflows/implement-shared-packages.md
Normal file
298
.windsurf/workflows/implement-shared-packages.md
Normal file
@ -0,0 +1,298 @@
|
||||
---
|
||||
description: Implement all 9 shared @bytelyst/* client packages from the SHARED_CLIENT_PACKAGES_ROADMAP
|
||||
---
|
||||
|
||||
# Implement Shared @bytelyst/\* Client Packages
|
||||
|
||||
## Pre-requisites
|
||||
|
||||
// turbo
|
||||
|
||||
1. Read the full roadmap doc:
|
||||
|
||||
```bash
|
||||
cat docs/roadmaps/SHARED_CLIENT_PACKAGES_ROADMAP.md
|
||||
```
|
||||
|
||||
// turbo 2. Study the canonical reference package structure:
|
||||
|
||||
```bash
|
||||
cat packages/feature-flag-client/package.json packages/feature-flag-client/tsconfig.json packages/feature-flag-client/src/index.ts packages/feature-flag-client/src/types.ts packages/feature-flag-client/src/client.ts packages/feature-flag-client/src/client.test.ts
|
||||
```
|
||||
|
||||
## Critical Rules
|
||||
|
||||
- **Package manager is pnpm** — NEVER use npm
|
||||
- **ESM everywhere** — `"type": "module"`, `.js` extensions in all imports
|
||||
- **No Node.js deps** — use `globalThis.fetch`, not node-fetch
|
||||
- **No React/RN deps** — pure TypeScript only
|
||||
- **Factory pattern** for API clients: `createXxxClient(config)` returning an interface
|
||||
- **No `console.log`**, no `any` type
|
||||
- **Every request to platform-service MUST include headers:**
|
||||
- `x-product-id` (from config.productId)
|
||||
- `Authorization: Bearer <token>` (from config.getAccessToken())
|
||||
- **Tests in `src/client.test.ts`** (co-located, same as `@bytelyst/feature-flag-client`)
|
||||
- **tsconfig.json must include `"lib": ["ES2022", "DOM"]`** and `"exclude": ["src/**/*.test.ts"]`
|
||||
- **All API interfaces, backend types, and ⚠️ warnings are in the roadmap doc** — follow them exactly
|
||||
|
||||
## Implementation (commit after each package)
|
||||
|
||||
### Package 1: `packages/referral-client/`
|
||||
|
||||
Create `packages/referral-client/` with:
|
||||
|
||||
- `package.json` — `@bytelyst/referral-client`, version 0.1.0
|
||||
- `tsconfig.json` — extends ../../tsconfig.base.json, lib: ["ES2022", "DOM"]
|
||||
- `src/types.ts` — ReferralDoc, ReferralClientConfig (from roadmap doc)
|
||||
- `src/client.ts` — `createReferralClient(config)` factory
|
||||
- `src/index.ts` — re-exports
|
||||
- `src/client.test.ts` — 8+ Vitest tests
|
||||
|
||||
⚠️ There is NO `/referrals/apply` endpoint. Use `PUT /referrals/:id` for status updates.
|
||||
|
||||
// turbo 3. Verify package 1:
|
||||
|
||||
```bash
|
||||
cd /Users/sd9235/code/mygh/learning_ai_common_plat && pnpm --filter @bytelyst/referral-client build && pnpm --filter @bytelyst/referral-client test
|
||||
```
|
||||
|
||||
4. Commit package 1:
|
||||
|
||||
```bash
|
||||
cd /Users/sd9235/code/mygh/learning_ai_common_plat && git add packages/referral-client/ && git commit -m "feat(referral-client): add @bytelyst/referral-client shared package"
|
||||
```
|
||||
|
||||
### Package 2: `packages/subscription-client/`
|
||||
|
||||
Create `packages/subscription-client/` with:
|
||||
|
||||
- `package.json` — `@bytelyst/subscription-client`, version 0.1.0
|
||||
- `tsconfig.json` — extends ../../tsconfig.base.json, lib: ["ES2022", "DOM"]
|
||||
- `src/types.ts` — SubscriptionDoc, PlanConfig, SubscriptionClientConfig (from roadmap doc)
|
||||
- `src/client.ts` — `createSubscriptionClient(config)` factory with caching
|
||||
- `src/index.ts` — re-exports
|
||||
- `src/client.test.ts` — 10+ Vitest tests
|
||||
|
||||
⚠️ `GET /plans` returns `{ plans: [...] }` — unwrap `.plans` in client.
|
||||
⚠️ Routes use `:userId` not `:id` — use `config.userId`.
|
||||
⚠️ `PlanConfig` fields `words`, `dictations`, `tokens` are LysnrAI-specific legacy. Use `features: string[]` for entitlements.
|
||||
|
||||
// turbo 5. Verify package 2:
|
||||
|
||||
```bash
|
||||
cd /Users/sd9235/code/mygh/learning_ai_common_plat && pnpm --filter @bytelyst/subscription-client build && pnpm --filter @bytelyst/subscription-client test
|
||||
```
|
||||
|
||||
6. Commit package 2:
|
||||
|
||||
```bash
|
||||
cd /Users/sd9235/code/mygh/learning_ai_common_plat && git add packages/subscription-client/ && git commit -m "feat(subscription-client): add @bytelyst/subscription-client shared package"
|
||||
```
|
||||
|
||||
### Package 3: `packages/celebrations/`
|
||||
|
||||
Create `packages/celebrations/` with:
|
||||
|
||||
- `package.json` — `@bytelyst/celebrations`, version 0.1.0
|
||||
- `tsconfig.json` — extends ../../tsconfig.base.json, lib: ["ES2022", "DOM"]
|
||||
- `src/types.ts` — CelebrationTrigger, Celebration, CelebrationConfig
|
||||
- `src/client.ts` — `createCelebrationEngine(config?)` factory
|
||||
- `src/index.ts` — re-exports
|
||||
- `src/client.test.ts` — 8+ Vitest tests
|
||||
|
||||
Pure TS, no backend. Products register custom triggers via `customTriggers` config. Messages ALWAYS positive.
|
||||
|
||||
// turbo 7. Verify package 3:
|
||||
|
||||
```bash
|
||||
cd /Users/sd9235/code/mygh/learning_ai_common_plat && pnpm --filter @bytelyst/celebrations build && pnpm --filter @bytelyst/celebrations test
|
||||
```
|
||||
|
||||
8. Commit package 3:
|
||||
|
||||
```bash
|
||||
cd /Users/sd9235/code/mygh/learning_ai_common_plat && git add packages/celebrations/ && git commit -m "feat(celebrations): add @bytelyst/celebrations shared package"
|
||||
```
|
||||
|
||||
### Package 4: `packages/gentle-notifications/`
|
||||
|
||||
Create `packages/gentle-notifications/` with:
|
||||
|
||||
- `package.json` — `@bytelyst/gentle-notifications`, version 0.1.0
|
||||
- `tsconfig.json` — extends ../../tsconfig.base.json, lib: ["ES2022", "DOM"]
|
||||
- `src/types.ts` — GentleNotificationConfig, GentleMessage
|
||||
- `src/client.ts` — `createGentleNotificationEngine(config?)` factory + `FORBIDDEN_PHRASES` export
|
||||
- `src/index.ts` — re-exports
|
||||
- `src/client.test.ts` — 8+ Vitest tests
|
||||
|
||||
Pure TS, no backend. Export `FORBIDDEN_PHRASES` constant. Support `registerMessages()` for product-specific pools.
|
||||
|
||||
// turbo 9. Verify package 4:
|
||||
|
||||
```bash
|
||||
cd /Users/sd9235/code/mygh/learning_ai_common_plat && pnpm --filter @bytelyst/gentle-notifications build && pnpm --filter @bytelyst/gentle-notifications test
|
||||
```
|
||||
|
||||
10. Commit package 4:
|
||||
|
||||
```bash
|
||||
cd /Users/sd9235/code/mygh/learning_ai_common_plat && git add packages/gentle-notifications/ && git commit -m "feat(gentle-notifications): add @bytelyst/gentle-notifications shared package"
|
||||
```
|
||||
|
||||
### Package 5: `packages/accessibility/`
|
||||
|
||||
Create `packages/accessibility/` with:
|
||||
|
||||
- `package.json` — `@bytelyst/accessibility`, version 0.1.0
|
||||
- `tsconfig.json` — extends ../../tsconfig.base.json, lib: ["ES2022", "DOM"]
|
||||
- `src/types.ts` — A11yProps interface
|
||||
- `src/client.ts` — label generator functions (buttonLabel, timerLabel, progressLabel, etc.)
|
||||
- `src/index.ts` — re-exports
|
||||
- `src/client.test.ts` — 10+ Vitest tests
|
||||
|
||||
Pure TS, no backend. Return A11yProps objects compatible with React Native accessibilityLabel/Role.
|
||||
|
||||
// turbo 11. Verify package 5:
|
||||
|
||||
```bash
|
||||
cd /Users/sd9235/code/mygh/learning_ai_common_plat && pnpm --filter @bytelyst/accessibility build && pnpm --filter @bytelyst/accessibility test
|
||||
```
|
||||
|
||||
12. Commit package 5:
|
||||
|
||||
```bash
|
||||
cd /Users/sd9235/code/mygh/learning_ai_common_plat && git add packages/accessibility/ && git commit -m "feat(accessibility): add @bytelyst/accessibility shared package"
|
||||
```
|
||||
|
||||
### Package 6: `packages/quick-actions/`
|
||||
|
||||
Create `packages/quick-actions/` with:
|
||||
|
||||
- `package.json` — `@bytelyst/quick-actions`, version 0.1.0
|
||||
- `tsconfig.json` — extends ../../tsconfig.base.json, lib: ["ES2022", "DOM"]
|
||||
- `src/types.ts` — QuickAction, ProgressiveSection, SmartDefault
|
||||
- `src/client.ts` — getVisibleSections, getAvailableActions, pickSmartDefault
|
||||
- `src/index.ts` — re-exports
|
||||
- `src/client.test.ts` — 6+ Vitest tests
|
||||
|
||||
Pure TS, no backend.
|
||||
|
||||
// turbo 13. Verify package 6:
|
||||
|
||||
```bash
|
||||
cd /Users/sd9235/code/mygh/learning_ai_common_plat && pnpm --filter @bytelyst/quick-actions build && pnpm --filter @bytelyst/quick-actions test
|
||||
```
|
||||
|
||||
14. Commit package 6:
|
||||
|
||||
```bash
|
||||
cd /Users/sd9235/code/mygh/learning_ai_common_plat && git add packages/quick-actions/ && git commit -m "feat(quick-actions): add @bytelyst/quick-actions shared package"
|
||||
```
|
||||
|
||||
### Package 7: `packages/time-references/`
|
||||
|
||||
Create `packages/time-references/` with:
|
||||
|
||||
- `package.json` — `@bytelyst/time-references`, version 0.1.0
|
||||
- `tsconfig.json` — extends ../../tsconfig.base.json, lib: ["ES2022", "DOM"]
|
||||
- `src/types.ts` — TimeReference interface
|
||||
- `src/client.ts` — getTimeReference, getEpisodeComparison, getEncouragingMessage, registerReferences
|
||||
- `src/index.ts` — re-exports
|
||||
- `src/client.test.ts` — 6+ Vitest tests
|
||||
|
||||
Pure TS, no backend. Support `registerReferences()` for custom reference databases.
|
||||
|
||||
// turbo 15. Verify package 7:
|
||||
|
||||
```bash
|
||||
cd /Users/sd9235/code/mygh/learning_ai_common_plat && pnpm --filter @bytelyst/time-references build && pnpm --filter @bytelyst/time-references test
|
||||
```
|
||||
|
||||
16. Commit package 7:
|
||||
|
||||
```bash
|
||||
cd /Users/sd9235/code/mygh/learning_ai_common_plat && git add packages/time-references/ && git commit -m "feat(time-references): add @bytelyst/time-references shared package"
|
||||
```
|
||||
|
||||
### Package 8: `packages/org-client/`
|
||||
|
||||
Create `packages/org-client/` with:
|
||||
|
||||
- `package.json` — `@bytelyst/org-client`, version 0.1.0
|
||||
- `tsconfig.json` — extends ../../tsconfig.base.json, lib: ["ES2022", "DOM"]
|
||||
- `src/types.ts` — OrganizationDoc, WorkspaceDoc, MembershipDoc, LicenseDoc, OrgClientConfig
|
||||
- `src/client.ts` — `createOrgClient(config)` factory
|
||||
- `src/index.ts` — re-exports
|
||||
- `src/client.test.ts` — 10+ Vitest tests
|
||||
|
||||
⚠️ All org routes require admin JWT role (`super_admin` or `admin`). Regular user tokens get 403.
|
||||
Covers orgs + workspaces + memberships + licenses (4 entity types).
|
||||
|
||||
// turbo 17. Verify package 8:
|
||||
|
||||
```bash
|
||||
cd /Users/sd9235/code/mygh/learning_ai_common_plat && pnpm --filter @bytelyst/org-client build && pnpm --filter @bytelyst/org-client test
|
||||
```
|
||||
|
||||
18. Commit package 8:
|
||||
|
||||
```bash
|
||||
cd /Users/sd9235/code/mygh/learning_ai_common_plat && git add packages/org-client/ && git commit -m "feat(org-client): add @bytelyst/org-client shared package"
|
||||
```
|
||||
|
||||
### Package 9: `packages/marketplace-client/`
|
||||
|
||||
Create `packages/marketplace-client/` with:
|
||||
|
||||
- `package.json` — `@bytelyst/marketplace-client`, version 0.1.0
|
||||
- `tsconfig.json` — extends ../../tsconfig.base.json, lib: ["ES2022", "DOM"]
|
||||
- `src/types.ts` — MarketplaceListingDoc, MarketplaceReviewDoc, MarketplaceInstallDoc, MarketplaceClientConfig, CreateListingInput
|
||||
- `src/client.ts` — `createMarketplaceClient(config)` factory
|
||||
- `src/index.ts` — re-exports
|
||||
- `src/client.test.ts` — 10+ Vitest tests
|
||||
|
||||
⚠️ NomGap's influencer.ts is product-specific. This is the GENERIC marketplace client.
|
||||
Covers listings + reviews + installs + reports.
|
||||
|
||||
// turbo 19. Verify package 9:
|
||||
|
||||
```bash
|
||||
cd /Users/sd9235/code/mygh/learning_ai_common_plat && pnpm --filter @bytelyst/marketplace-client build && pnpm --filter @bytelyst/marketplace-client test
|
||||
```
|
||||
|
||||
20. Commit package 9:
|
||||
|
||||
```bash
|
||||
cd /Users/sd9235/code/mygh/learning_ai_common_plat && git add packages/marketplace-client/ && git commit -m "feat(marketplace-client): add @bytelyst/marketplace-client shared package"
|
||||
```
|
||||
|
||||
## Final Verification
|
||||
|
||||
// turbo 21. Run full workspace verification:
|
||||
|
||||
```bash
|
||||
cd /Users/sd9235/code/mygh/learning_ai_common_plat && pnpm build && pnpm test && pnpm typecheck
|
||||
```
|
||||
|
||||
22. Update roadmap status — in `docs/roadmaps/SHARED_CLIENT_PACKAGES_ROADMAP.md`, change `> **Status:** Not Started` to `> **Status:** ✅ Complete — 9 packages, ~76 tests`
|
||||
|
||||
23. Final commit:
|
||||
|
||||
```bash
|
||||
cd /Users/sd9235/code/mygh/learning_ai_common_plat && git add docs/roadmaps/SHARED_CLIENT_PACKAGES_ROADMAP.md && git commit -m "docs: mark shared client packages roadmap as complete"
|
||||
```
|
||||
|
||||
24. Push all commits:
|
||||
|
||||
```bash
|
||||
cd /Users/sd9235/code/mygh/learning_ai_common_plat && git push origin main
|
||||
```
|
||||
|
||||
## DO NOT
|
||||
|
||||
- Do NOT modify any files outside `packages/` and the roadmap doc
|
||||
- Do NOT create packages in any other directory
|
||||
- Do NOT install external dependencies — these packages have zero deps
|
||||
- Do NOT skip tests — every package must have passing Vitest tests
|
||||
- Do NOT modify platform-service or any product repo
|
||||
- Do NOT use npm — this is a pnpm workspace
|
||||
923
docs/roadmaps/SHARED_CLIENT_PACKAGES_ROADMAP.md
Normal file
923
docs/roadmaps/SHARED_CLIENT_PACKAGES_ROADMAP.md
Normal file
@ -0,0 +1,923 @@
|
||||
# Shared @bytelyst/\* Client Packages — Extraction from Product Repos
|
||||
|
||||
> **Status:** Not Started
|
||||
> **Priority:** High — eliminates duplication across 9+ products
|
||||
> **Estimated effort:** 8–10 sessions
|
||||
> **Owner:** Cascade agent working in `learning_ai_common_plat`
|
||||
|
||||
---
|
||||
|
||||
## Context & Motivation
|
||||
|
||||
During NomGap (learning_ai_fastgap) Phase 5 buildout, we created several pure-TS engine modules that are **product-agnostic** and should live in `learning_ai_common_plat/packages/` as shared `@bytelyst/*` packages. The same patterns apply (or will apply) to all 9+ ByteLyst products: LysnrAI, MindLyst, ChronoMind, JarvisJr, NomGap, PeakPulse, FlowMonk, NoteLett, ActionTrail.
|
||||
|
||||
Additionally, platform-service already has backend modules for referrals, subscriptions, plans, orgs, and licenses — but **no corresponding client packages** exist for products to consume them. Each product has been hand-rolling its own fetch calls or building bespoke modules.
|
||||
|
||||
### What already exists in platform-service (port 4003)
|
||||
|
||||
> **IMPORTANT:** All platform-service endpoints require an `x-product-id` header (read by `getRequestProductId(req)`). Client packages MUST send this header on every request.
|
||||
|
||||
| Backend Module | Exact Endpoints | Auth | Cosmos Container |
|
||||
| ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |
|
||||
| `modules/referrals/` | `GET /referrals`, `POST /referrals`, `PUT /referrals/:id`, `GET /referrals/stats`, `GET /referrals/by-referrer/:referrerId`, `GET /referrals/by-email/:email` | JWT | `referrals` (`/referrerId`) |
|
||||
| `modules/subscriptions/` | `GET /subscriptions/:userId`, `POST /subscriptions`, `PUT /subscriptions/:userId` (NOTE: keyed by userId, not id) | JWT | `subscriptions` (`/userId`) |
|
||||
| `modules/plans/` | `GET /plans` → `{ plans: [...] }`, `GET /plans/:name`, `POST /plans`, `PUT /plans/:id`, `POST /plans/seed` | JWT | `plans` (`/productId`) |
|
||||
| `modules/orgs/` | `GET/POST /orgs`, `GET/PATCH /orgs/:id`, workspaces: `GET/POST /orgs/:id/workspaces`, `PATCH /orgs/:id/workspaces/:workspaceId`, memberships: `GET/POST /orgs/:id/memberships`, `PATCH /orgs/:id/memberships/:membershipId` | **Admin-only** (`super_admin` or `admin` JWT role) | `organizations` (`/productId`), `workspaces` (`/orgId`), `memberships` (`/orgId`) |
|
||||
| `modules/licenses/` | Generate, activate, deactivate, list by user | JWT | `licenses` (`/userId`) |
|
||||
| `modules/marketplace/` | Listings CRUD + certification + reviews + installs + reports + votes (6 entity types, 309 lines of types) | JWT (some public) | `marketplace_listings` (`/productId`), `marketplace_reviews` (`/listingId`), `marketplace_installs` (`/userId`), etc. |
|
||||
|
||||
### What NomGap built as product-specific (should be shared)
|
||||
|
||||
| NomGap File | What It Does | Should Become |
|
||||
| --------------------------------- | --------------------------------------------------------------------- | -------------------------------- |
|
||||
| `src/lib/referral.ts` | Referral code share links, apply codes, stats | `@bytelyst/referral-client` |
|
||||
| `src/lib/monetization.ts` | Plan definitions, Pro feature gate, trial, restore | `@bytelyst/monetization` |
|
||||
| `src/lib/b2b-wellness.ts` | Org dashboard, team challenges, licenses, SSO | `@bytelyst/org-client` |
|
||||
| `src/lib/influencer.ts` | Branded challenges, affiliate tracking, share cards | `@bytelyst/marketplace-client` |
|
||||
| `src/lib/celebrations.ts` | 14 celebration triggers, haptics, confetti, sounds, positive messages | `@bytelyst/celebrations` |
|
||||
| `src/lib/gentle-notifications.ts` | ND-friendly messaging, adaptive frequency, forbidden phrases | `@bytelyst/gentle-notifications` |
|
||||
| `src/lib/accessibility.ts` | VoiceOver/TalkBack label generators for common UI patterns | `@bytelyst/accessibility` |
|
||||
| `src/lib/quick-actions.ts` | Progressive disclosure, smart defaults, action definitions | `@bytelyst/quick-actions` |
|
||||
| `src/lib/time-blindness.ts` | Familiar duration references ("About as long as a movie") | `@bytelyst/time-references` |
|
||||
|
||||
---
|
||||
|
||||
## Packages to Create (Priority Order)
|
||||
|
||||
### Tier 1 — Highest Leverage (backend already exists, every product needs these)
|
||||
|
||||
#### 1. `@bytelyst/referral-client`
|
||||
|
||||
**Purpose:** Browser/React Native-safe client for platform-service `/referrals/*` endpoints.
|
||||
|
||||
**Reference implementation:** `learning_ai_fastgap/src/lib/referral.ts` (73 lines)
|
||||
|
||||
**Backend already exists:** `platform-service/src/modules/referrals/` — full CRUD + webhook dispatch + Zod schemas.
|
||||
|
||||
**Backend types (from `referrals/types.ts`):**
|
||||
|
||||
```ts
|
||||
interface ReferralDoc {
|
||||
id: string;
|
||||
productId: string;
|
||||
referrerId: string;
|
||||
referrerEmail: string;
|
||||
referredUserId: string | null;
|
||||
referredEmail: string;
|
||||
status: 'pending' | 'signed_up' | 'subscribed' | 'rewarded';
|
||||
referrerRewardTokens: number;
|
||||
referredRewardTokens: number;
|
||||
referrerRewarded: boolean;
|
||||
referredRewarded: boolean;
|
||||
createdAt: string;
|
||||
completedAt: string | null;
|
||||
}
|
||||
```
|
||||
|
||||
**Public API to implement:**
|
||||
|
||||
```ts
|
||||
export interface ReferralClientConfig {
|
||||
baseUrl: string; // e.g. 'http://localhost:4003/api'
|
||||
productId: string; // sent as x-product-id header on every request
|
||||
getAccessToken: () => string | null;
|
||||
// Defaults used when creating referrals (so callers only provide referredEmail)
|
||||
defaultRewardTokens?: { referrer: number; referred: number }; // default: 1000 / 500
|
||||
}
|
||||
|
||||
export interface ReferralClient {
|
||||
// Core CRUD (delegates to platform-service)
|
||||
listMyReferrals(referrerId: string): Promise<{ referrals: ReferralDoc[]; count: number }>;
|
||||
getReferralStats(): Promise<{ total: number; completed: number; rewarded: number }>;
|
||||
createReferral(input: {
|
||||
referrerId: string; // from auth context
|
||||
referrerEmail: string; // from auth context
|
||||
referredEmail: string; // user input
|
||||
}): Promise<ReferralDoc>;
|
||||
// NOTE: There is NO /referrals/apply endpoint in platform-service.
|
||||
// "Applying" a referral = calling PUT /referrals/:id to update status to 'signed_up'.
|
||||
// The client should expose this as a semantic method:
|
||||
updateReferralStatus(
|
||||
id: string,
|
||||
referrerId: string,
|
||||
status: ReferralDoc['status']
|
||||
): Promise<ReferralDoc>;
|
||||
getByEmail(email: string): Promise<ReferralDoc | null>;
|
||||
|
||||
// Client-side helpers (pure TS, no network)
|
||||
buildShareLink(code: string): string;
|
||||
buildShareMessage(code: string, productName: string): string;
|
||||
calculateEarnedDays(conversions: number, daysPerReferral?: number): number;
|
||||
}
|
||||
|
||||
export function createReferralClient(config: ReferralClientConfig): ReferralClient;
|
||||
```
|
||||
|
||||
**⚠️ Backend gap:** Platform-service has no "apply referral code" endpoint. The flow for a referred user is:
|
||||
|
||||
1. `getByEmail(referredEmail)` → finds existing pending referral
|
||||
2. `updateReferralStatus(id, referrerId, 'signed_up')` → marks as completed
|
||||
3. Backend fires webhook via `dispatchReferralStatusChanged()`
|
||||
|
||||
If a single "apply code" endpoint is needed, a new `POST /referrals/apply` route should be added to platform-service first.
|
||||
|
||||
**Pattern:** Follow `@bytelyst/feature-flag-client` exactly — `createXxxClient()` factory, `types.ts` + `client.ts` + `index.ts`, `globalThis.fetch`, no Node.js deps. Every request MUST include `x-product-id` and `Authorization: Bearer <token>` headers.
|
||||
|
||||
**Tests:** 8+ Vitest tests — create, listMyReferrals, stats, updateStatus, getByEmail, buildShareLink, buildShareMessage, calculateEarnedDays, error handling.
|
||||
|
||||
---
|
||||
|
||||
#### 2. `@bytelyst/subscription-client`
|
||||
|
||||
**Purpose:** Client for platform-service `/subscriptions/*` + `/plans/*` — check entitlements, manage trials, restore purchases.
|
||||
|
||||
**Reference implementation:** `learning_ai_fastgap/src/lib/monetization.ts` (200 lines)
|
||||
|
||||
**Backend already exists:** `platform-service/src/modules/subscriptions/` + `platform-service/src/modules/plans/`
|
||||
|
||||
**Backend types (from `subscriptions/types.ts`):**
|
||||
|
||||
```ts
|
||||
interface SubscriptionDoc {
|
||||
id: string;
|
||||
productId: string;
|
||||
userId: string;
|
||||
plan: 'free' | 'pro' | 'enterprise';
|
||||
status: 'active' | 'cancelled' | 'past_due' | 'trialing';
|
||||
currentPeriodStart: string;
|
||||
currentPeriodEnd: string;
|
||||
cancelAtPeriodEnd: boolean;
|
||||
monthlyPrice: number;
|
||||
tokensIncluded: number;
|
||||
tokensUsed: number;
|
||||
stripeCustomerId?: string;
|
||||
stripeSubscriptionId?: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
```
|
||||
|
||||
**Backend types (from `plans/types.ts`):**
|
||||
|
||||
```ts
|
||||
interface PlanConfig {
|
||||
id: string;
|
||||
productId: string;
|
||||
name: string;
|
||||
displayName: string;
|
||||
price: number;
|
||||
tokens: number;
|
||||
words: number;
|
||||
dictations: number;
|
||||
features: string[];
|
||||
stripePriceId?: string;
|
||||
active: boolean;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
```
|
||||
|
||||
**Public API to implement:**
|
||||
|
||||
```ts
|
||||
export interface SubscriptionClientConfig {
|
||||
baseUrl: string;
|
||||
productId: string; // sent as x-product-id header on every request
|
||||
userId: string; // REQUIRED — subscription routes are keyed by userId, not id
|
||||
getAccessToken: () => string | null;
|
||||
storage?: { getItem(k: string): string | null; setItem(k: string, v: string): void };
|
||||
}
|
||||
|
||||
export interface SubscriptionClient {
|
||||
// Server-authoritative checks (all hit platform-service)
|
||||
getMySubscription(): Promise<SubscriptionDoc | null>;
|
||||
// NOTE: GET /plans returns { plans: [...] } — client must unwrap .plans
|
||||
getPlans(): Promise<PlanConfig[]>;
|
||||
startTrial(planName?: string): Promise<SubscriptionDoc>;
|
||||
cancelSubscription(): Promise<SubscriptionDoc>;
|
||||
// NOTE: PUT /subscriptions/:userId (not :id) — use config.userId
|
||||
updateSubscription(updates: Partial<SubscriptionDoc>): Promise<SubscriptionDoc>;
|
||||
|
||||
// Client-side helpers (cached, offline-safe)
|
||||
isPro(): boolean; // cached: plan !== 'free' && status === 'active' | 'trialing'
|
||||
isTrialing(): boolean; // cached: status === 'trialing'
|
||||
hasFeature(feature: string): boolean; // cached: looks up PlanConfig.features.includes(feature)
|
||||
daysRemaining(): number | null; // cached: days until currentPeriodEnd
|
||||
getCachedSubscription(): SubscriptionDoc | null;
|
||||
getCachedPlans(): PlanConfig[];
|
||||
refresh(): Promise<void>; // re-fetch from server + update cache
|
||||
}
|
||||
|
||||
export function createSubscriptionClient(config: SubscriptionClientConfig): SubscriptionClient;
|
||||
```
|
||||
|
||||
**⚠️ Backend gaps & notes:**
|
||||
|
||||
- **No restore-purchase endpoint** — Platform-service has no store receipt verification. `restorePurchase()` was removed from the client API. To support IAP restore, either: (a) add a `POST /subscriptions/restore` endpoint to platform-service that verifies Apple/Google receipts, or (b) products verify receipts client-side and call `updateSubscription()` with the new status. Recommend (a) as a future platform-service enhancement.
|
||||
- **`PlanConfig.features` is the entitlement source** — `hasFeature('ai_coaching')` checks `PlanConfig.features.includes('ai_coaching')` from the cached plan matching the user's subscription tier. Products define their own feature strings.
|
||||
- **`PlanConfig` fields `words`, `dictations`, `tokens`** are LysnrAI-specific legacy fields. Other products should ignore them and rely on `features: string[]` for entitlement checks. The `PlanConfig` type should be treated as extensible.
|
||||
- **Subscription routes use `:userId` not `:id`** — `GET /subscriptions/:userId` and `PUT /subscriptions/:userId`. The client must use `config.userId` for these calls.
|
||||
|
||||
**Key design:** Cache subscription + plans in `storage` for offline reads. `isPro()`, `hasFeature()`, `isTrialing()` read from cache (never block on network). `refresh()` fetches from server and updates cache.
|
||||
|
||||
**Tests:** 10+ Vitest tests — getMySubscription, getPlans (unwraps .plans), startTrial, isPro, hasFeature (with features array), isTrialing, daysRemaining, cache persistence, refresh, error handling.
|
||||
|
||||
---
|
||||
|
||||
#### 3. `@bytelyst/celebrations`
|
||||
|
||||
**Purpose:** Product-agnostic celebration engine — milestone triggers, haptic configs, confetti, sounds, positive reinforcement messages.
|
||||
|
||||
**Reference implementation:** `learning_ai_fastgap/src/lib/celebrations.ts` (190 lines)
|
||||
|
||||
**No backend needed** — pure client-side TS.
|
||||
|
||||
**Public API to implement:**
|
||||
|
||||
```ts
|
||||
export type CelebrationTrigger =
|
||||
| 'task_completed'
|
||||
| 'streak_continued'
|
||||
| 'streak_milestone'
|
||||
| 'achievement_unlocked'
|
||||
| 'level_up'
|
||||
| 'personal_best'
|
||||
| 'milestone_reached'
|
||||
| 'goal_completed'
|
||||
| 'first_action'
|
||||
| 'halfway'
|
||||
| 'session_completed'
|
||||
| 'session_started';
|
||||
|
||||
export interface Celebration {
|
||||
id: string;
|
||||
title: string;
|
||||
body: string;
|
||||
emoji: string;
|
||||
hapticType: 'light' | 'medium' | 'heavy' | 'success' | 'warning';
|
||||
confetti: boolean;
|
||||
sound: 'chime' | 'success' | 'level_up' | 'none';
|
||||
}
|
||||
|
||||
export interface CelebrationConfig {
|
||||
// Products register their own trigger→message mappings
|
||||
customTriggers?: Record<string, Celebration>;
|
||||
}
|
||||
|
||||
export function createCelebrationEngine(config?: CelebrationConfig): {
|
||||
getCelebration(trigger: CelebrationTrigger | string): Celebration;
|
||||
getTimedCelebrations(elapsedMs: number, targetMs: number, shownIds: Set<string>): Celebration[];
|
||||
isPersonalBest(current: number, previous: number): boolean;
|
||||
getPositiveMessage(progressPercent: number): string;
|
||||
// For early breaks / incomplete actions — NEVER negative
|
||||
getPositiveIncompleteMessage(progressPercent: number): string;
|
||||
};
|
||||
```
|
||||
|
||||
**Key design:** Products register custom triggers via `customTriggers` config. Default triggers are universal (streaks, achievements, levels). Messages are ALWAYS positive — never guilt-inducing.
|
||||
|
||||
**Tests:** 8+ tests — all trigger types, custom triggers, timed celebrations, personal best, positive messages, incomplete messages.
|
||||
|
||||
---
|
||||
|
||||
#### 4. `@bytelyst/gentle-notifications`
|
||||
|
||||
**Purpose:** Neurodivergent-friendly notification messaging system — encouraging tone, adaptive frequency, forbidden phrases.
|
||||
|
||||
**Reference implementation:** `learning_ai_fastgap/src/lib/gentle-notifications.ts` (120 lines)
|
||||
|
||||
**No backend needed** — pure client-side TS.
|
||||
|
||||
**Public API to implement:**
|
||||
|
||||
```ts
|
||||
export interface GentleNotificationConfig {
|
||||
maxPerHour: number;
|
||||
tone: 'encouraging' | 'neutral' | 'minimal';
|
||||
adaptiveFrequency: boolean;
|
||||
dismissCount: number;
|
||||
suppressThreshold: number;
|
||||
}
|
||||
|
||||
export interface GentleMessage {
|
||||
title: string;
|
||||
body: string;
|
||||
tone: 'encouraging' | 'neutral' | 'minimal';
|
||||
}
|
||||
|
||||
export function createGentleNotificationEngine(config?: Partial<GentleNotificationConfig>): {
|
||||
getDefaultConfig(): GentleNotificationConfig;
|
||||
getMessage(type: string, config?: GentleNotificationConfig): GentleMessage;
|
||||
shouldSuppress(config: GentleNotificationConfig): boolean;
|
||||
recordDismissal(config: GentleNotificationConfig): GentleNotificationConfig;
|
||||
resetDismissals(config: GentleNotificationConfig): GentleNotificationConfig;
|
||||
|
||||
// Products register their own message pools
|
||||
registerMessages(type: string, messages: GentleMessage[]): void;
|
||||
|
||||
// Ecosystem-wide forbidden phrases — NEVER use these in any notification
|
||||
getForbiddenPhrases(): readonly string[];
|
||||
containsForbiddenPhrase(text: string): boolean;
|
||||
};
|
||||
|
||||
// Exported constant for lint rules / CI checks
|
||||
export const FORBIDDEN_PHRASES: readonly string[];
|
||||
```
|
||||
|
||||
**Forbidden phrases (ecosystem-wide policy):**
|
||||
|
||||
```ts
|
||||
[
|
||||
"You haven't",
|
||||
'You forgot',
|
||||
"Don't forget",
|
||||
'You should have',
|
||||
"Why didn't you",
|
||||
'You missed',
|
||||
'You failed',
|
||||
'You need to',
|
||||
];
|
||||
```
|
||||
|
||||
**Key design:** Products register their own message pools via `registerMessages()`. The forbidden phrases list is an ecosystem-wide policy enforced across all products. `containsForbiddenPhrase()` can be used in CI/lint.
|
||||
|
||||
**Tests:** 8+ tests — getMessage, shouldSuppress, recordDismissal, resetDismissals, registerMessages, forbiddenPhrases, containsForbiddenPhrase, adaptive frequency reduction.
|
||||
|
||||
---
|
||||
|
||||
#### 5. `@bytelyst/accessibility`
|
||||
|
||||
**Purpose:** VoiceOver/TalkBack/Dynamic Type accessibility label generators for common UI patterns used across all products.
|
||||
|
||||
**Reference implementation:** `learning_ai_fastgap/src/lib/accessibility.ts` (220 lines)
|
||||
|
||||
**No backend needed** — pure client-side TS.
|
||||
|
||||
**Public API to implement:**
|
||||
|
||||
```ts
|
||||
export interface A11yProps {
|
||||
accessible: boolean;
|
||||
accessibilityLabel: string;
|
||||
accessibilityHint?: string;
|
||||
accessibilityRole?:
|
||||
| 'button'
|
||||
| 'header'
|
||||
| 'text'
|
||||
| 'timer'
|
||||
| 'progressbar'
|
||||
| 'image'
|
||||
| 'alert'
|
||||
| 'summary'
|
||||
| 'adjustable';
|
||||
accessibilityState?: {
|
||||
disabled?: boolean;
|
||||
selected?: boolean;
|
||||
checked?: boolean;
|
||||
busy?: boolean;
|
||||
expanded?: boolean;
|
||||
};
|
||||
accessibilityValue?: { min?: number; max?: number; now?: number; text?: string };
|
||||
}
|
||||
|
||||
// Common UI pattern label generators
|
||||
export function buttonLabel(label: string, hint?: string): A11yProps;
|
||||
export function timerLabel(status: string, elapsedText: string, context?: string): A11yProps;
|
||||
export function progressLabel(name: string, percent: number, description?: string): A11yProps;
|
||||
export function sliderLabel(metric: string, value: number, max?: number): A11yProps;
|
||||
export function alertLabel(severity: string, message: string): A11yProps;
|
||||
export function achievementLabel(name: string, description: string, earned: boolean): A11yProps;
|
||||
export function streakLabel(current: number, longest: number): A11yProps;
|
||||
export function listItemLabel(title: string, subtitle?: string, badge?: string): A11yProps;
|
||||
|
||||
// Utilities
|
||||
export function formatDurationForA11y(ms: number): string; // "16 hours 30 minutes"
|
||||
export function formatNumberForA11y(n: number): string; // "1,234" → "one thousand two hundred thirty four"
|
||||
export function buildAnnouncement(headline: string, detail: string): string;
|
||||
|
||||
// Positive messaging (never negative, complements celebrations package)
|
||||
export function getPositiveBreakMessage(progressPercent: number): string;
|
||||
```
|
||||
|
||||
**Key design:** React Native `accessibilityLabel` / `accessibilityRole` compatible. Web apps can map to `aria-label` / `role`. Every label function returns a complete `A11yProps` object ready to spread onto a component.
|
||||
|
||||
**Tests:** 10+ tests — each label generator, formatDurationForA11y, formatNumberForA11y, buildAnnouncement, positiveBreakMessage, edge cases (0%, 100%, negative).
|
||||
|
||||
---
|
||||
|
||||
### Tier 2 — High Value (no backend dependency)
|
||||
|
||||
#### 6. `@bytelyst/quick-actions`
|
||||
|
||||
**Purpose:** Progressive disclosure system, smart defaults, quick action definitions for reducing cognitive load.
|
||||
|
||||
**Reference implementation:** `learning_ai_fastgap/src/lib/quick-actions.ts` (170 lines)
|
||||
|
||||
**Public API to implement:**
|
||||
|
||||
```ts
|
||||
export interface QuickAction {
|
||||
id: string;
|
||||
label: string;
|
||||
icon: string;
|
||||
shortLabel: string;
|
||||
action: string;
|
||||
requiresAuth: boolean;
|
||||
}
|
||||
|
||||
export interface ProgressiveSection {
|
||||
id: string;
|
||||
title: string;
|
||||
defaultExpanded: boolean;
|
||||
priority: 'primary' | 'secondary' | 'detail';
|
||||
}
|
||||
|
||||
export interface SmartDefault {
|
||||
key: string;
|
||||
value: unknown;
|
||||
source: 'last_used' | 'most_common' | 'recommendation' | 'system';
|
||||
}
|
||||
|
||||
export function getVisibleSections(
|
||||
sections: ProgressiveSection[],
|
||||
expandedIds: Set<string>
|
||||
): ProgressiveSection[];
|
||||
export function getAvailableActions(
|
||||
actions: QuickAction[],
|
||||
context: { isActive?: boolean; isAuthenticated?: boolean }
|
||||
): QuickAction[];
|
||||
export function pickSmartDefault(candidates: SmartDefault[]): SmartDefault | null;
|
||||
|
||||
export const MAX_VISIBLE_ITEMS = 3;
|
||||
export const MAX_VISIBLE_LIST = 5;
|
||||
```
|
||||
|
||||
**Tests:** 6+ tests.
|
||||
|
||||
---
|
||||
|
||||
#### 7. `@bytelyst/time-references`
|
||||
|
||||
**Purpose:** Familiar duration references for time-blindness aids ("About as long as a movie", "3 episodes of The Office").
|
||||
|
||||
**Reference implementation:** `learning_ai_fastgap/src/lib/time-blindness.ts` (175 lines)
|
||||
|
||||
**Public API to implement:**
|
||||
|
||||
```ts
|
||||
export interface TimeReference {
|
||||
text: string;
|
||||
emoji: string;
|
||||
category: 'media' | 'activity' | 'travel' | 'nature';
|
||||
}
|
||||
|
||||
export function getTimeReference(elapsedHours: number): TimeReference;
|
||||
export function getEpisodeComparison(
|
||||
elapsedHours: number,
|
||||
showName?: string,
|
||||
episodeMins?: number
|
||||
): string;
|
||||
export function getEncouragingMessage(elapsedHours: number): string;
|
||||
|
||||
// Products can register custom reference databases
|
||||
export function registerReferences(
|
||||
entries: Array<{ minHours: number; maxHours: number; references: TimeReference[] }>
|
||||
): void;
|
||||
```
|
||||
|
||||
**Tests:** 6+ tests.
|
||||
|
||||
---
|
||||
|
||||
### Tier 3 — Backend Client Wrappers
|
||||
|
||||
#### 8. `@bytelyst/org-client`
|
||||
|
||||
**Purpose:** Client for platform-service `/orgs/*` + `/licenses/*` — B2B org management, workspace management, membership management.
|
||||
|
||||
**Reference implementation:** `learning_ai_fastgap/src/lib/b2b-wellness.ts` (100 lines) — but the actual backend is far richer.
|
||||
|
||||
**Backend already exists:** `platform-service/src/modules/orgs/` (3 entity types) + `platform-service/src/modules/licenses/`
|
||||
|
||||
**⚠️ Auth requirement:** All org routes require **admin-only** access (`super_admin` or `admin` JWT role via `requireAdmin(req)`). Regular user tokens will get `403 Forbidden`.
|
||||
|
||||
**Backend types (from `orgs/types.ts`):**
|
||||
|
||||
```ts
|
||||
interface OrganizationDoc {
|
||||
id: string; // org_<uuid>
|
||||
productId: string;
|
||||
name: string;
|
||||
slug: string;
|
||||
status: 'active' | 'disabled';
|
||||
ownerUserId: string;
|
||||
metadata?: Record<string, unknown>;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
interface WorkspaceDoc {
|
||||
id: string; // ws_<uuid>
|
||||
orgId: string;
|
||||
productId: string;
|
||||
name: string;
|
||||
slug: string;
|
||||
status: 'active' | 'archived';
|
||||
description?: string;
|
||||
metadata?: Record<string, unknown>;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
interface MembershipDoc {
|
||||
id: string; // mbr_<orgId>_<userId>_<scope>
|
||||
orgId: string;
|
||||
productId: string;
|
||||
scope: 'org' | 'workspace';
|
||||
workspaceId?: string;
|
||||
userId: string;
|
||||
role: 'owner' | 'admin' | 'member' | 'viewer';
|
||||
status: 'active' | 'invited' | 'disabled';
|
||||
invitedBy?: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
interface LicenseDoc {
|
||||
id: string;
|
||||
productId: string;
|
||||
key: string; // LYSNR-XXXX-XXXX-XXXX format
|
||||
userId: string;
|
||||
plan: 'free' | 'pro' | 'enterprise';
|
||||
status: 'active' | 'revoked' | 'expired';
|
||||
activatedAt: string | null;
|
||||
expiresAt: string | null;
|
||||
deviceIds: string[];
|
||||
maxDevices: number;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
```
|
||||
|
||||
**Public API to implement:**
|
||||
|
||||
```ts
|
||||
export interface OrgClientConfig {
|
||||
baseUrl: string;
|
||||
productId: string;
|
||||
getAccessToken: () => string | null;
|
||||
}
|
||||
|
||||
export interface OrgClient {
|
||||
// Organizations
|
||||
listOrgs(query?: { status?: string; limit?: number }): Promise<OrganizationDoc[]>;
|
||||
createOrg(input: { name: string; slug: string; ownerUserId?: string }): Promise<OrganizationDoc>;
|
||||
getOrg(id: string): Promise<OrganizationDoc>;
|
||||
updateOrg(id: string, updates: Partial<OrganizationDoc>): Promise<OrganizationDoc>;
|
||||
|
||||
// Workspaces
|
||||
listWorkspaces(orgId: string): Promise<WorkspaceDoc[]>;
|
||||
createWorkspace(
|
||||
orgId: string,
|
||||
input: { name: string; slug: string; description?: string }
|
||||
): Promise<WorkspaceDoc>;
|
||||
updateWorkspace(
|
||||
orgId: string,
|
||||
workspaceId: string,
|
||||
updates: Partial<WorkspaceDoc>
|
||||
): Promise<WorkspaceDoc>;
|
||||
|
||||
// Memberships
|
||||
listMemberships(
|
||||
orgId: string,
|
||||
query?: { scope?: string; limit?: number }
|
||||
): Promise<MembershipDoc[]>;
|
||||
addMember(
|
||||
orgId: string,
|
||||
input: { userId: string; role?: string; scope?: string; workspaceId?: string }
|
||||
): Promise<MembershipDoc>;
|
||||
updateMember(
|
||||
orgId: string,
|
||||
membershipId: string,
|
||||
updates: { role?: string; status?: string }
|
||||
): Promise<MembershipDoc>;
|
||||
|
||||
// Licenses
|
||||
generateLicense(input: {
|
||||
userId: string;
|
||||
plan: string;
|
||||
maxDevices?: number;
|
||||
}): Promise<LicenseDoc>;
|
||||
activateLicense(input: { key: string; deviceId: string }): Promise<LicenseDoc>;
|
||||
deactivateLicense(input: { key: string; deviceId: string }): Promise<void>;
|
||||
}
|
||||
|
||||
export function createOrgClient(config: OrgClientConfig): OrgClient;
|
||||
```
|
||||
|
||||
**Tests:** 10+ Vitest tests — listOrgs, createOrg, getOrg, listWorkspaces, createWorkspace, listMemberships, addMember, generateLicense, activateLicense, error handling (403 for non-admin).
|
||||
|
||||
---
|
||||
|
||||
#### 9. `@bytelyst/marketplace-client`
|
||||
|
||||
**Purpose:** Client for platform-service `/marketplace/*` — template marketplace with listings, reviews, installs, certification, reports.
|
||||
|
||||
**Reference implementation:** `learning_ai_fastgap/src/lib/influencer.ts` (95 lines) covers a subset. The actual backend is a full marketplace with 6 entity types.
|
||||
|
||||
**Backend already exists:** `platform-service/src/modules/marketplace/` — 309 lines of types, 6 entity types.
|
||||
|
||||
**⚠️ Note:** NomGap's `influencer.ts` (branded challenges, affiliate tracking) is a **product-specific layer** on top of the marketplace. The shared client should expose the generic marketplace API. Products build influencer/branded features on top.
|
||||
|
||||
**Backend types (key entities from `marketplace/types.ts`):**
|
||||
|
||||
```ts
|
||||
interface MarketplaceListingDoc {
|
||||
id: string; // lst_<uuid>
|
||||
productId: string;
|
||||
templateType: string; // product-specific: 'fasting_protocol', 'agent_persona', 'timer_preset', etc.
|
||||
authorId: string;
|
||||
authorName: string;
|
||||
title: string;
|
||||
shortDescription: string;
|
||||
description: string;
|
||||
tags: string[];
|
||||
category: string;
|
||||
payload: Record<string, unknown>; // product-specific JSON
|
||||
pricingModel: 'free' | 'paid' | 'freemium';
|
||||
priceInCents: number;
|
||||
certificationStatus: 'draft' | 'submitted' | 'in_review' | 'approved' | 'rejected' | 'suspended';
|
||||
installCount: number;
|
||||
reviewCount: number;
|
||||
averageRating: number;
|
||||
visibility: 'private' | 'unlisted' | 'public';
|
||||
featured: boolean;
|
||||
version: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
interface MarketplaceReviewDoc {
|
||||
id: string; // rev_<uuid>
|
||||
listingId: string;
|
||||
productId: string;
|
||||
authorId: string;
|
||||
rating: number; // 1-5
|
||||
title: string;
|
||||
body: string;
|
||||
verified: boolean;
|
||||
createdAt: string;
|
||||
}
|
||||
|
||||
interface MarketplaceInstallDoc {
|
||||
id: string; // inst_<uuid>
|
||||
listingId: string;
|
||||
productId: string;
|
||||
userId: string;
|
||||
version: string;
|
||||
installedAt: string;
|
||||
uninstalledAt: string | null;
|
||||
}
|
||||
```
|
||||
|
||||
**Public API to implement:**
|
||||
|
||||
```ts
|
||||
export interface MarketplaceClientConfig {
|
||||
baseUrl: string;
|
||||
productId: string;
|
||||
getAccessToken: () => string | null;
|
||||
}
|
||||
|
||||
export interface MarketplaceClient {
|
||||
// Listings
|
||||
listListings(query?: {
|
||||
templateType?: string;
|
||||
category?: string;
|
||||
tags?: string;
|
||||
pricingModel?: string;
|
||||
sortBy?: string;
|
||||
q?: string;
|
||||
limit?: number;
|
||||
offset?: number;
|
||||
}): Promise<{ listings: MarketplaceListingDoc[]; total: number }>;
|
||||
getListing(id: string): Promise<MarketplaceListingDoc>;
|
||||
createListing(input: CreateListingInput): Promise<MarketplaceListingDoc>;
|
||||
updateListing(
|
||||
id: string,
|
||||
updates: Partial<MarketplaceListingDoc>
|
||||
): Promise<MarketplaceListingDoc>;
|
||||
submitForCertification(id: string, notes?: string): Promise<MarketplaceListingDoc>;
|
||||
|
||||
// Installs
|
||||
installListing(listingId: string): Promise<MarketplaceInstallDoc>;
|
||||
uninstallListing(listingId: string): Promise<void>;
|
||||
listMyInstalls(query?: { limit?: number; offset?: number }): Promise<MarketplaceInstallDoc[]>;
|
||||
|
||||
// Reviews
|
||||
listReviews(
|
||||
listingId: string,
|
||||
query?: { sortBy?: string; limit?: number }
|
||||
): Promise<MarketplaceReviewDoc[]>;
|
||||
createReview(
|
||||
listingId: string,
|
||||
input: { rating: number; title: string; body: string }
|
||||
): Promise<MarketplaceReviewDoc>;
|
||||
|
||||
// Reports
|
||||
reportListing(listingId: string, input: { reason: string; details: string }): Promise<void>;
|
||||
}
|
||||
|
||||
export function createMarketplaceClient(config: MarketplaceClientConfig): MarketplaceClient;
|
||||
```
|
||||
|
||||
**Tests:** 10+ Vitest tests — listListings (with filters), getListing, createListing, submitForCertification, installListing, listMyInstalls, listReviews, createReview, reportListing, error handling.
|
||||
|
||||
---
|
||||
|
||||
## Implementation Instructions
|
||||
|
||||
### For each package, follow this exact pattern:
|
||||
|
||||
```
|
||||
packages/<package-name>/
|
||||
├── package.json # @bytelyst/<name>, version 0.1.0, type: module
|
||||
├── tsconfig.json # extends ../../tsconfig.base.json
|
||||
├── vitest.config.ts # (optional) only if tests need special config; root config handles most cases
|
||||
├── src/
|
||||
│ ├── index.ts # re-exports from client.ts + types.ts
|
||||
│ ├── types.ts # all interfaces + config types
|
||||
│ └── client.ts # createXxxClient() factory or pure functions
|
||||
└── tests/
|
||||
└── client.test.ts # Vitest tests
|
||||
```
|
||||
|
||||
### package.json template:
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "@bytelyst/<package-name>",
|
||||
"version": "0.1.0",
|
||||
"type": "module",
|
||||
"description": "<one-line description>",
|
||||
"exports": {
|
||||
".": {
|
||||
"import": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts"
|
||||
}
|
||||
},
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
"files": ["dist"],
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"test": "vitest run"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> **Note on devDependencies:** `typescript` and `vitest` are provided by the pnpm workspace root and do NOT need to be listed as devDependencies in individual packages. The root `vitest.config.ts` (with `passWithNoTests: true`) handles all packages. Only add a per-package `vitest.config.ts` if tests need special setup (e.g., custom globals or test environment).
|
||||
|
||||
### tsconfig.json template:
|
||||
|
||||
```json
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "dist",
|
||||
"rootDir": "src"
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
```
|
||||
|
||||
### Conventions (MUST follow):
|
||||
|
||||
- **ESM everywhere:** `"type": "module"`, `.js` extensions in imports
|
||||
- **No Node.js deps:** Use `globalThis.fetch`, not node-fetch
|
||||
- **No React/RN deps:** Pure TS only — consumed by web, mobile, Node.js
|
||||
- **Factory pattern** for API clients: `createXxxClient(config)` returning an interface
|
||||
- **Storage optional:** Accept `{ getItem, setItem }` for cache/persistence
|
||||
- **Vitest tests:** Every public function must have at least one test
|
||||
- **No `console.log`** — silent by default
|
||||
- **No `any` type** — use explicit types or generics
|
||||
|
||||
### After creating packages:
|
||||
|
||||
1. Add each to `pnpm-workspace.yaml` (already covered by `packages/*` glob)
|
||||
2. Run `pnpm build` to verify all compile
|
||||
3. Run `pnpm test` to verify all tests pass
|
||||
4. Run `pnpm typecheck` to verify no type errors
|
||||
|
||||
### After packages are built, migrate product repos:
|
||||
|
||||
For each product (NomGap first, then others):
|
||||
|
||||
1. Add `"@bytelyst/<package>": "file:../../learning_ai_common_plat/packages/<package>"` to product's `package.json`
|
||||
2. Replace product-specific module with thin wrapper that delegates to shared package
|
||||
3. Remove duplicated code
|
||||
4. Run product's `npm test && npm run typecheck`
|
||||
|
||||
---
|
||||
|
||||
## Execution Order
|
||||
|
||||
| # | Package | Tier | Est. Lines | Backend Dep | Tests |
|
||||
| --------- | -------------------------------- | ---- | ---------- | -------------------------------------- | ------ |
|
||||
| 1 | `@bytelyst/referral-client` | 1 | ~150 | Yes (referrals) | 8 |
|
||||
| 2 | `@bytelyst/subscription-client` | 1 | ~220 | Yes (subscriptions + plans) | 10 |
|
||||
| 3 | `@bytelyst/celebrations` | 1 | ~150 | No | 8 |
|
||||
| 4 | `@bytelyst/gentle-notifications` | 1 | ~130 | No | 8 |
|
||||
| 5 | `@bytelyst/accessibility` | 1 | ~200 | No | 10 |
|
||||
| 6 | `@bytelyst/quick-actions` | 2 | ~120 | No | 6 |
|
||||
| 7 | `@bytelyst/time-references` | 2 | ~150 | No | 6 |
|
||||
| 8 | `@bytelyst/org-client` | 3 | ~250 | Yes (orgs + licenses) — **admin-only** | 10 |
|
||||
| 9 | `@bytelyst/marketplace-client` | 3 | ~200 | Yes (marketplace — 6 entity types) | 10 |
|
||||
| **Total** | | | **~1,570** | | **76** |
|
||||
|
||||
---
|
||||
|
||||
## Verification
|
||||
|
||||
After all packages are created:
|
||||
|
||||
```bash
|
||||
cd learning_ai_common_plat
|
||||
pnpm build && pnpm test && pnpm typecheck
|
||||
```
|
||||
|
||||
After NomGap migration:
|
||||
|
||||
```bash
|
||||
cd learning_ai_fastgap
|
||||
npm test && npm run typecheck
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Products That Will Consume These Packages
|
||||
|
||||
> ✅ = immediate consumer, ⭐ = primary driver (has existing code to migrate), — = not needed initially (can adopt later)
|
||||
|
||||
| Package | NomGap | LysnrAI | MindLyst | ChronoMind | JarvisJr | PeakPulse | FlowMonk | NoteLett | ActionTrail |
|
||||
| -------------------- | ------ | ------- | -------- | ---------- | -------- | --------- | -------- | -------- | ----------- |
|
||||
| referral-client | ⭐ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||
| subscription-client | ⭐ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||
| celebrations | ⭐ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||
| gentle-notifications | ⭐ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||
| accessibility | ⭐ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||
| quick-actions | ⭐ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||
| time-references | ⭐ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||
| org-client | ⭐ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||
| marketplace-client | ✅ | — | — | — | ⭐ | — | — | ✅ | — |
|
||||
|
||||
> **Notes on matrix:**
|
||||
>
|
||||
> - `time-references` is useful for ANY app with timers, sessions, streaks, or progress tracking — not just fasting apps. Products customize the reference database via `registerReferences()`.
|
||||
> - `org-client` is relevant for all products if they offer a B2B tier. Initially only products with active B2B plans need it.
|
||||
> - `marketplace-client` is currently relevant for NomGap (fasting protocols) and JarvisJr (agent personas). Other products can adopt it when they add marketplace features.
|
||||
|
||||
---
|
||||
|
||||
## NomGap Migration Plan (First Product)
|
||||
|
||||
After the shared packages are built, NomGap (`learning_ai_fastgap`) should be migrated first since it has the most existing code to replace.
|
||||
|
||||
### Files to replace in NomGap:
|
||||
|
||||
| NomGap File | Replace With | Action |
|
||||
| --------------------------------------------- | -------------------------------- | ------------------------------------------------------------------------------------------- |
|
||||
| `src/lib/referral.ts` (73 lines) | `@bytelyst/referral-client` | Rewrite as thin wrapper: `import { createReferralClient } from '@bytelyst/referral-client'` |
|
||||
| `src/lib/monetization.ts` (200 lines) | `@bytelyst/subscription-client` | Rewrite: `createSubscriptionClient(config)` + product-specific plan names |
|
||||
| `src/lib/b2b-wellness.ts` (100 lines) | `@bytelyst/org-client` | Rewrite: `createOrgClient(config)` + NomGap-specific wellness types on top |
|
||||
| `src/lib/influencer.ts` (95 lines) | `@bytelyst/marketplace-client` | Rewrite: `createMarketplaceClient(config)` + influencer layer on top |
|
||||
| `src/lib/celebrations.ts` (190 lines) | `@bytelyst/celebrations` | Rewrite: `createCelebrationEngine({ customTriggers: nomgapTriggers })` |
|
||||
| `src/lib/gentle-notifications.ts` (120 lines) | `@bytelyst/gentle-notifications` | Rewrite: `createGentleNotificationEngine()` + `registerMessages('fasting', [...])` |
|
||||
| `src/lib/accessibility.ts` (220 lines) | `@bytelyst/accessibility` | Rewrite: import shared label generators + NomGap-specific labels |
|
||||
| `src/lib/quick-actions.ts` (170 lines) | `@bytelyst/quick-actions` | Rewrite: import shared functions + NomGap-specific action/section defs |
|
||||
| `src/lib/time-blindness.ts` (175 lines) | `@bytelyst/time-references` | Rewrite: `import { getTimeReference } from '@bytelyst/time-references'` |
|
||||
|
||||
### Steps:
|
||||
|
||||
1. Add `file:` deps to `package.json`: `"@bytelyst/referral-client": "file:../../learning_ai_common_plat/packages/referral-client"`, etc.
|
||||
2. For each file, replace the standalone implementation with a thin wrapper that delegates to the shared package
|
||||
3. Keep any NomGap-specific logic (fasting-specific celebrations, meal-specific suggestions) in the product repo
|
||||
4. Run `npm test && npm run typecheck` after each migration
|
||||
5. Total estimated reduction: ~1,340 lines of NomGap-specific code replaced with ~300 lines of thin wrappers
|
||||
|
||||
### Other product migrations:
|
||||
|
||||
After NomGap is migrated, other products can adopt packages as needed. Each product adds `file:` deps and creates thin wrappers. No product needs to migrate all 9 packages at once — adopt incrementally.
|
||||
|
||||
---
|
||||
|
||||
## Known Backend Gaps (Require Platform-Service Changes)
|
||||
|
||||
These gaps were identified during the review. They should be addressed in platform-service BEFORE or IN PARALLEL with the client packages:
|
||||
|
||||
| Gap | Impact | Recommendation |
|
||||
| -------------------------------------------------------------------------- | ----------------------------------------------- | ----------------------------------------------------------------------------------- |
|
||||
| No `POST /referrals/apply` endpoint | Referred users can't "apply" a code in one step | Add a `POST /referrals/apply` route that finds by email + updates status atomically |
|
||||
| No `POST /subscriptions/restore` endpoint | IAP restore requires receipt verification | Add a store receipt verification endpoint (Apple/Google JWKS) |
|
||||
| `PlanConfig` has LysnrAI-specific fields (`words`, `dictations`, `tokens`) | Other products ignore these fields | Consider making plan schema extensible with `metadata: Record<string, unknown>` |
|
||||
| Org routes require admin JWT role | Regular users can't view their own org | Consider adding user-facing org endpoints (e.g., `GET /my/org`) |
|
||||
Loading…
Reference in New Issue
Block a user