# Common Platform Analysis > **Purpose:** Identify duplicated code across `learning_voice_ai_agent` (LysnrAI) and `learning_multimodal_memory_agents` (MindLyst) that should be extracted into `learning_ai_common_plat` as shared packages. > > **Date:** 2026-02-12 --- ## Executive Summary After scanning both repos, there are **8 high-value extraction candidates** across 3 tiers (TypeScript services, Next.js dashboards, design system). The heaviest duplication is in the LysnrAI monorepo where 4 Fastify microservices each contain near-identical copies of 5 foundational files. The MindLyst repo shares the same design token values and will benefit from a shared design system package. --- ## 1. Duplicated Components — TypeScript Microservices ### 1.1 Cosmos DB Client Singleton **Duplication:** 4 near-identical copies across LysnrAI services + 2 in dashboards = **6 copies** | Location | Lines | | --------------------------------------------- | ----- | | `services/platform-service/src/lib/cosmos.ts` | 25 | | `services/billing-service/src/lib/cosmos.ts` | 25 | | `services/growth-service/src/lib/cosmos.ts` | 25 | | `services/tracker-service/src/lib/cosmos.ts` | 25 | | `admin-dashboard-web/src/lib/cosmos.ts` | 112 | | `user-dashboard-web/src/lib/cosmos.ts` | 94 | **Pattern:** All do: lazy singleton `CosmosClient`, read `COSMOS_ENDPOINT` + `COSMOS_KEY` + `COSMOS_DATABASE` from env, expose `getContainer(name)`. Dashboard versions add container registry with partition key definitions and TTL config. **Proposed shared package:** `@bytelyst/cosmos` ``` packages/cosmos/ src/ client.ts — getCosmosClient(), getDatabase(), getContainer() containers.ts — container registry (names, partition keys, TTL) types.ts — ContainerConfig interface index.ts package.json ``` --- ### 1.2 Service Error Classes **Duplication:** 4 copies across LysnrAI services, each slightly different | Location | Error Classes | | ------------------------------------ | --------------------------------------------------------------------- | | `platform-service/src/lib/errors.ts` | ServiceError, NotFound, BadRequest, Unauthorized, Forbidden | | `billing-service/src/lib/errors.ts` | ServiceError, NotFound, BadRequest, Forbidden, TooManyRequests | | `growth-service/src/lib/errors.ts` | ServiceError, NotFound, BadRequest, Forbidden | | `tracker-service/src/lib/errors.ts` | ServiceError, NotFound, BadRequest, Unauthorized, Forbidden, Conflict | **Pattern:** All share the same `ServiceError` base class with `statusCode` + `message`. Subclasses vary per service (some have Unauthorized, some have Conflict, etc.). **Proposed shared package:** `@bytelyst/errors` ``` packages/errors/ src/ service-error.ts — base ServiceError class http-errors.ts — NotFound, BadRequest, Unauthorized, Forbidden, Conflict, TooManyRequests index.ts package.json ``` --- ### 1.3 JWT Auth Middleware **Duplication:** 3 copies in LysnrAI services + 2 in dashboards = **5 copies** | Location | Variant | | -------------------------------------------- | ------------------------------------------------------------ | | `platform-service/src/modules/auth/jwt.ts` | Full: create + verify (issuer service) | | `tracker-service/src/lib/auth.ts` | Verify-only (consumer service) | | `billing-service` (via internal key) | Different pattern | | `admin-dashboard-web/src/lib/auth-server.ts` | create + verify + bcrypt + authenticateUser | | `user-dashboard-web/src/lib/auth-server.ts` | create + verify + bcrypt + authenticateUser (near-identical) | **Pattern:** All use `jose` library, HS256, same `getSecret()` → `TextEncoder.encode(JWT_SECRET)`. Dashboards add bcrypt password hashing and `getCurrentUser()`. **Proposed shared package:** `@bytelyst/auth` ``` packages/auth/ src/ jwt.ts — createAccessToken, createRefreshToken, verifyToken (configurable issuer/expiry) middleware.ts — extractAuth (Fastify), getCurrentUser (Next.js) password.ts — hashPassword, verifyPassword (bcryptjs wrapper) types.ts — AuthPayload, TokenPayload interfaces index.ts package.json ``` --- ### 1.4 Fastify Server Bootstrap **Duplication:** 4 copies in LysnrAI services | Location | Lines | | -------------------------------- | ----- | | `platform-service/src/server.ts` | 87 | | `billing-service/src/server.ts` | 101 | | `growth-service/src/server.ts` | 79 | | `tracker-service/src/server.ts` | 84 | **Pattern:** All follow the same structure: 1. Create Fastify instance with logger 2. Register CORS (parse `CORS_ORIGIN`) 3. Register Swagger (service-specific title/description) 4. Register Prometheus metrics 5. Add `x-request-id` hook (propagate or generate UUID) 6. Add `/health` endpoint (same shape: `{ status, service, version, timestamp, requestId }`) 7. Set error handler (check `instanceof ServiceError`) 8. Register route modules with `/api` prefix 9. Start listener **Proposed shared package:** `@bytelyst/fastify-core` ``` packages/fastify-core/ src/ create-app.ts — createServiceApp({ name, version, description }) → configured Fastify instance request-id.ts — x-request-id hook (reusable plugin) health.ts — health check route factory error-handler.ts — ServiceError-aware error handler index.ts package.json ``` --- ### 1.5 Zod Config Loader **Duplication:** 4 copies in LysnrAI services | Location | Shared Fields | | ------------------------------------ | --------------------------------------------------------------------------------------------- | | `platform-service/src/lib/config.ts` | PORT, HOST, NODE_ENV, CORS_ORIGIN, SERVICE_NAME, COSMOS_ENDPOINT, COSMOS_KEY, COSMOS_DATABASE | | `billing-service/src/lib/config.ts` | Same base + STRIPE*\*, BILLING*\* | | `growth-service/src/lib/config.ts` | Same base + STRIPE*\*, WEBHOOK*\* | | `tracker-service/src/lib/config.ts` | Same base + JWT_SECRET, DEFAULT_PRODUCT_ID | **Pattern:** Every service has 7 identical base fields (PORT, HOST, NODE_ENV, CORS_ORIGIN, SERVICE_NAME, COSMOS_ENDPOINT, COSMOS_KEY, COSMOS_DATABASE) and then service-specific extensions. **Proposed shared package:** `@bytelyst/config` ``` packages/config/ src/ base-schema.ts — baseEnvSchema (Zod object with the 8 common fields) loader.ts — loadConfig(extendedSchema?) merges base + service-specific index.ts package.json ``` --- ### 1.6 Product Config / Identity **Duplication:** 4 copies in LysnrAI services + 1 JSON canonical source | Location | Content | | -------------------------------------------- | ----------------------------------------------------------- | --- | ---------- | | `shared/product.json` | Canonical: `{ productId, displayName, licensePrefix, ... }` | | `platform-service/src/lib/product-config.ts` | `PRODUCT_ID = "lysnrai"` (hardcoded) | | `billing-service/src/lib/product-config.ts` | Same (hardcoded) | | `growth-service/src/lib/product-config.ts` | Same (hardcoded) | | `tracker-service/src/lib/product-config.ts` | `DEFAULT_PRODUCT_ID = env | | "lysnrai"` | **Pattern:** Every service hardcodes or reads the same product identity. Should read from a shared source. **Proposed:** Include in `@bytelyst/config` — export `loadProductConfig()` that reads `shared/product.json` or env vars. Each consuming project provides its own `product.json`. --- ## 2. Duplicated Components — Next.js Dashboards ### 2.1 Auth Context (React) **Duplication:** 3 copies across LysnrAI dashboards | Location | Variant | | ----------------------------------------------- | ------------------------------------------------------------- | | `admin-dashboard-web/src/lib/auth-context.tsx` | AdminUser, localStorage keys: `admin_*` | | `user-dashboard-web/src/lib/auth-context.tsx` | PortalUser, localStorage keys: `portal_*`, SSO cookie support | | `tracker-dashboard-web/src/lib/` (auth context) | TrackerUser, localStorage keys: `tracker_*` | **Pattern:** All implement: `AuthProvider`, `useAuth()`, localStorage-backed user + token storage, login/logout. Differ only in: user type, storage key prefix, and SSO support. **Proposed shared package:** `@bytelyst/react-auth` ``` packages/react-auth/ src/ auth-context.tsx — generic AuthProvider, configurable storage keys + login endpoint use-auth.ts — typed useAuth() hook types.ts — BaseUser interface, AuthConfig index.ts package.json ``` --- ### 2.2 API Fetch Utility **Duplication:** 4+ copies across dashboards and service clients | Location | Pattern | | ------------------------------------------------- | ------------------------------------------------ | | `admin-dashboard-web/src/lib/api.ts` | `apiFetch(path, options)` → `{ data, error }` | | `user-dashboard-web/src/lib/platform-client.ts` | `request(path, options)` → `T` (throws) | | `user-dashboard-web/src/lib/billing-client.ts` | Same `request` pattern | | `user-dashboard-web/src/lib/growth-client.ts` | Same `request` pattern | | `tracker-dashboard-web/src/lib/tracker-client.ts` | `apiFetch` with Bearer token | **Pattern:** All wrap `fetch()` with: base URL, JSON headers, auth header injection, error parsing. Two variants: result-tuple `{ data, error }` vs throw-on-error. **Proposed shared package:** `@bytelyst/api-client` ``` packages/api-client/ src/ client.ts — createApiClient({ baseUrl, getToken?, headers? }) → { fetch, safeFetch } types.ts — ApiResult, ApiError index.ts package.json ``` --- ### 2.3 Utility Functions **Duplication:** Identical `cn()` function in 2 dashboards | Location | | -------------------------------------- | | `admin-dashboard-web/src/lib/utils.ts` | | `user-dashboard-web/src/lib/utils.ts` | **Pattern:** `cn(...inputs) → twMerge(clsx(inputs))` — standard shadcn/ui utility. **Proposed:** Include in `@bytelyst/ui-utils` or just in `@bytelyst/react-auth` as a peer export. --- ## 3. Duplicated Components — Design System ### 3.1 Design Tokens **Duplication:** Same color/spacing/typography values maintained across **5 formats** in MindLyst + referenced in LysnrAI dashboards | Location | Format | | ------------------------------------------- | ------------------------------------------ | | `design-system/tokens/mindlyst.tokens.json` | Canonical JSON | | `shared/src/.../theme/MindLystTokens.kt` | KMP Kotlin object | | `iosApp/MindLystTheme.swift` | SwiftUI structs | | `androidApp/.../MindLystTheme.kt` | Compose theme | | `web/src/styles/globals.css` | CSS custom properties | | `design-system/web/mindlyst.css` | CSS custom properties (duplicate of above) | **Note:** LysnrAI's admin/user dashboards use TailwindCSS + shadcn/ui with a different color system currently. If both products converge on a shared design language, the token JSON can be the single source. **Proposed shared package:** `@bytelyst/design-tokens` ``` packages/design-tokens/ tokens/ colors.json — palette, semantic dark/light, brain gradients typography.json — font families, sizes, weights, line heights spacing.json — 8pt grid scale radius.json — border radius scale motion.json — duration + easing layout.json — breakpoints, gutter, max-width generated/ — auto-generated from tokens JSON tokens.css — CSS custom properties (--ml-*) tokens.ts — TypeScript constants tokens.kt — Kotlin object (for KMP) tokens.swift — Swift structs (for iOS) scripts/ generate.ts — reads JSON, outputs all formats package.json ``` --- ## 4. Potential Future Shared Components These are not exact duplicates yet but represent patterns that will diverge if not unified: | Component | LysnrAI | MindLyst | Shared Potential | | --------------------------- | ------------------------------------- | -------------------------- | ------------------------------------- | | **OpenAI API client** | Python `src/llm/` + Azure OpenAI | KMP `api/OpenAIClient.kt` | Shared TS client for backend services | | **Whisper/STT client** | Python `src/audio/azure_stt.py` | KMP `api/WhisperClient.kt` | Both need speech-to-text | | **Health check aggregator** | `services/monitoring/health-check.ts` | N/A (could reuse) | Generic multi-service health poller | | **Docker Compose patterns** | Full stack compose | Planned | Shared compose fragments | | **CI workflow templates** | 9 GitHub Actions | 1 CI workflow | Reusable workflow templates | | **Python Cosmos client** | `src/cloud/cosmos_client.py` | N/A | If MindLyst adds a Python backend | --- ## 5. Recommended Package Structure ``` learning_ai_common_plat/ ├── packages/ │ ├── cosmos/ — Azure Cosmos DB client singleton + container registry │ ├── errors/ — Typed HTTP service errors (ServiceError hierarchy) │ ├── auth/ — JWT create/verify + bcrypt + Fastify middleware │ ├── fastify-core/ — Server bootstrap, request-id, health check, error handler │ ├── config/ — Zod base env schema + product identity loader │ ├── api-client/ — Typed fetch wrapper for dashboards/service clients │ ├── react-auth/ — AuthProvider + useAuth() for Next.js dashboards │ └── design-tokens/ — Canonical token JSON + generators for CSS/TS/Kotlin/Swift ├── docs/ │ ├── COMMON_PLATFORM_ANALYSIS.md (this file) │ └── MIGRATION_GUIDE.md (how to adopt in each repo) ├── package.json — workspace root (npm/pnpm workspaces) ├── tsconfig.base.json — shared TypeScript config └── README.md ``` --- ## 6. Migration Priority Ordered by **impact × ease**: | Priority | Package | Impact | Effort | Reason | | -------- | ------------------------- | ------ | ------ | ----------------------------------------------------- | | **P0** | `@bytelyst/errors` | High | Low | Drop-in, no config, 6 consumers | | **P0** | `@bytelyst/cosmos` | High | Low | 6 identical files, most-used utility | | **P1** | `@bytelyst/config` | High | Medium | Eliminates 4 config files + 4 product-config files | | **P1** | `@bytelyst/auth` | High | Medium | 5 copies of JWT logic, security-critical | | **P1** | `@bytelyst/fastify-core` | High | Medium | 4 nearly identical server.ts files (~350 lines saved) | | **P2** | `@bytelyst/api-client` | Medium | Low | 5+ fetch wrappers in dashboards | | **P2** | `@bytelyst/react-auth` | Medium | Medium | 3 auth contexts, saves ~400 lines | | **P3** | `@bytelyst/design-tokens` | Medium | High | Cross-platform token generation pipeline | --- ## 7. Consumption Model ### TypeScript packages (services + dashboards) - Publish as **npm workspace packages** or use **git submodule** + `file:` references - Each consuming repo adds `"@bytelyst/": "file:../learning_ai_common_plat/packages/"` to `package.json` - Alternatively, publish to a private npm registry (GitHub Packages) ### Design tokens - `design-tokens` package exports a CLI: `npx @bytelyst/design-tokens generate --format css,ts,kt,swift` - Each consuming repo runs the generator as a build step or commits the generated output ### KMP (Kotlin Multiplatform) - MindLyst's `MindLystTokens.kt` could be auto-generated from `design-tokens/tokens/*.json` - Add a Gradle task in `shared/build.gradle.kts` that reads the JSON and emits Kotlin --- ## 8. Lines of Code Impact | Category | Current Duplicated LOC | After Extraction | | -------------------------- | ---------------------- | ------------------------------------ | | Cosmos client (6 files) | ~306 | ~50 (one shared) | | Error classes (4 files) | ~157 | ~45 (one shared) | | JWT auth (5 files) | ~250 | ~60 (one shared) | | Server bootstrap (4 files) | ~351 | ~30 (per-service config only) | | Config loader (4 files) | ~107 | ~10 (per-service extension only) | | Product config (4 files) | ~35 | 0 (read from shared) | | API fetch utils (5 files) | ~200 | ~30 (one shared) | | Auth context (3 files) | ~450 | ~50 (one shared, configured per-app) | | **Total** | **~1,856 LOC** | **~275 LOC** | **Net savings: ~1,580 lines of duplicated code eliminated**, plus single-point maintenance for security-critical auth and database infrastructure. --- ## 9. Next Steps 1. Initialize `learning_ai_common_plat` as a pnpm/npm workspace monorepo 2. Start with P0 packages (`errors`, `cosmos`) — lowest risk, highest copy count 3. Add tests (port existing service tests that cover these utilities) 4. Update LysnrAI services to import from `@bytelyst/*` instead of local `./lib/*` 5. Wire MindLyst's Next.js web app to use shared design tokens 6. Set up CI to test the common platform packages independently