Extends the browser-side vault client with five new methods that
mirror the server-side Phase 9 routes, plus a new
`awaiting-recovery-code` state that pauses the unlock mid-flow
when the server is in zero-knowledge mode.
VaultUnlockState gains a fourth variant
---------------------------------------
| { status: 'awaiting-recovery-code' }
This is the state the client sits in between calling unlock()
(which received a recovery blob from GET /key) and the user typing
their recovery code into the UI. The settings page status badge
got updated to render this case as "🔑 Recovery-Code erforderlich".
New closure state inside createVaultClient
------------------------------------------
- pendingRecoveryBlob: stash for the recovery wrap returned by
GET /key in zero-knowledge mode. unlockWithRecoveryCode reads
from here so the second round of input doesn't need a re-fetch.
- cachedUnwrappedMkBytes: kept ONLY when the vault was unlocked
via the recovery code path AND the user might want to disable
zero-knowledge later (which needs to hand the MK back to the
server for KEK re-wrapping). The standard unlock path leaves
this null because the server already has the KEK wrap. Wiped
on lock(), on disable success, and on any state transition
that destroys the master key.
Modified existing methods
-------------------------
- unlock(): branches on the response shape. If the server returns
a recovery blob (zero-knowledge mode), stash it via
awaitRecoveryCode() and return state='awaiting-recovery-code'.
Otherwise unwrap as before. Same fork applies to the /init
fallback path.
- rotate(): if the server somehow returned a ZK shape (it should
never — rotate is forbidden in ZK mode server-side), bail with
a server error instead of silently misinterpreting bytes.
- lock(): also clears pendingRecoveryBlob + wipes
cachedUnwrappedMkBytes.
New methods (all wired into the returned VaultClient)
-----------------------------------------------------
- setupRecoveryCode(): generates a fresh 32-byte recovery secret,
derives the wrap key, re-fetches the active master key in
extractable form, seals it, posts to /recovery-wrap, returns
the formatted recovery code for the UI to display. Wipes both
raw byte references after the seal. Caller is responsible for
clearing the formatted string from memory once the user has
confirmed they backed it up.
- clearRecoveryCode(): DELETE /recovery-wrap. Server enforces the
"not while ZK is active" rule.
- enableZeroKnowledge(): POST /zero-knowledge { enable: true }.
Maps RECOVERY_WRAP_MISSING server response to a clear "set up
a recovery code first" client error.
- disableZeroKnowledge(): POST /zero-knowledge { enable: false,
masterKey: base64 }. Reads the cached MK bytes, base64-encodes,
sends. Wipes the cache after success.
- unlockWithRecoveryCode(code): completes the flow that started
in unlock(). Parses the user-typed code (RecoveryCodeFormatError
bubbles up if the shape is wrong), derives the wrap key, runs a
single inline AES-GCM decrypt on the stashed blob (yields both
the raw bytes for the cache AND a non-extractable runtime key
for the provider), wipes raw bytes, transitions to 'unlocked'.
Generic error message on failure ("wrong recovery code or
corrupted vault") so an attacker can't distinguish wrong-code
from tampered-blob. Stays in 'awaiting-recovery-code' on
failure so the user can retry without a re-fetch.
Drive-by stale test fix
-----------------------
aes.test.ts had an assertion from Phase 1 that `tasks` and `events`
return null because they were on enabled:false. Phase 7.1 flipped
both tables on, so the assertion has been failing since that
commit. Replaced the test with a stable negative case
(non-existent table name) that doesn't shift with each rollout
phase.
Test results: 78/78 crypto tests pass after the fix.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
||
|---|---|---|
| .. | ||
| apps | ||
| .gitignore | ||
| CLAUDE.md | ||
| README.md | ||
Mana Apps
A unified application ecosystem built on a shared authentication system, supporting multiple branded applications across web and mobile platforms.
Overview
Mana Apps is a monorepo containing web and mobile applications that provide organization management, team collaboration, and credit transfer capabilities. The system supports multiple branded applications (Memoro, Cards, Storyteller, Mana) through a flexible multi-tenant architecture.
Applications
- Web App (
apps/web) - SvelteKit-based web application - Mobile App (
apps/mobile) - React Native (Expo) app for iOS, Android, and web - Landing (
apps/landing) - Landing page (planned)
Features
- 🔐 Unified authentication with Supabase
- 🏢 Organization management with role-based access
- 👥 Team collaboration and member management
- 💰 Mana credit system with transfers and balance tracking
- 🎨 Multi-brand support with configurable themes
- 📱 Cross-platform (Web, iOS, Android)
- 🔄 Real-time updates across all platforms
- 🧪 Comprehensive testing with Vitest and Playwright
Quick Start
Prerequisites
- Node.js 20+
- pnpm (for web app)
- npm (for mobile app)
- Supabase account with project configured
- Expo CLI (for mobile development)
Setup
-
Clone the repository
git clone <repository-url> cd mana-core-apps -
Web App Setup
cd apps/web pnpm install cp .env.example .env # Edit .env with your Supabase credentials pnpm dev -
Mobile App Setup
cd apps/mobile npm install cp .env.example .env # Edit .env with your Supabase credentials npm start
Project Structure
mana-core-apps/
├── apps/
│ ├── web/ # SvelteKit web application
│ │ ├── src/
│ │ │ ├── routes/ # File-based routing
│ │ │ │ ├── (auth)/ # Public auth pages
│ │ │ │ └── (app)/ # Protected pages
│ │ │ ├── lib/
│ │ │ │ ├── components/
│ │ │ │ ├── config/ # Multi-app configuration
│ │ │ │ ├── server/ # Server-only utilities
│ │ │ │ └── types/
│ │ │ └── hooks.server.ts # Auth middleware
│ │ └── package.json
│ │
│ ├── mobile/ # React Native (Expo) app
│ │ ├── app/ # File-based routing (Expo Router)
│ │ │ ├── (drawer)/ # Drawer navigation
│ │ │ ├── auth/ # Auth screens
│ │ │ └── _layout.tsx # Root layout with auth
│ │ ├── components/ # React components
│ │ ├── utils/ # Utilities (Supabase, storage)
│ │ └── package.json
│ │
│ └── landing/ # Landing page (planned)
│
├── CLAUDE.md # Developer documentation
└── README.md # This file
Technology Stack
Web App (apps/web)
| Category | Technology |
|---|---|
| Framework | SvelteKit 2 with Svelte 5 (Runes) |
| Language | TypeScript |
| Styling | TailwindCSS 3 with PostCSS |
| Database | Supabase (PostgreSQL) |
| Auth | Supabase Auth with SSR |
| Testing | Vitest (unit) + Playwright (E2E) |
| Build Tool | Vite |
Mobile App (apps/mobile)
| Category | Technology |
|---|---|
| Framework | Expo 52 with React Native 0.76 |
| Language | TypeScript |
| Routing | Expo Router 4 (file-based) |
| Styling | NativeWind (TailwindCSS for RN) |
| Navigation | React Navigation (drawer, tabs) |
| Database | Supabase |
| Build | EAS Build |
| Platforms | iOS, Android, Web |
Development
Web App Commands
cd apps/web
# Development
pnpm dev # Start dev server (http://localhost:5173)
pnpm build # Build for production
pnpm preview # Preview production build
# Code Quality
pnpm check # Type-check with svelte-check
pnpm check:watch # Type-check in watch mode
pnpm lint # Check formatting and lint
pnpm format # Format code with Prettier
# Testing
pnpm test # Run unit tests (Vitest)
pnpm test:ui # Run tests with UI
pnpm test:e2e # Run E2E tests (Playwright)
Mobile App Commands
cd apps/mobile
# Development
npm start # Start Expo dev server
npm run ios # Run on iOS simulator
npm run android # Run on Android emulator
npm run web # Run web version (http://localhost:19006)
# Building
npm run build:dev # Build dev client
npm run build:preview # Build for internal testing
npm run build:prod # Build for production
# Code Quality
npm run lint # Lint and check formatting
npm run format # Fix linting and format code
# Setup
npm run prebuild # Generate native projects
Environment Configuration
Both apps require Supabase configuration. Create .env files based on .env.example:
Web App (apps/web/.env)
PUBLIC_SUPABASE_URL=your_supabase_project_url
PUBLIC_SUPABASE_ANON_KEY=your_supabase_anon_key
MIDDLEWARE_URL=https://mana-middleware-111768794939.europe-west3.run.app
PUBLIC_APP_NAME=Mana Web
NODE_ENV=development
Mobile App (apps/mobile/.env)
EXPO_PUBLIC_SUPABASE_URL=your_supabase_project_url
EXPO_PUBLIC_SUPABASE_ANON_KEY=your_supabase_anon_key
Architecture
Multi-Tenant System
The system supports multiple branded applications sharing the same authentication backend:
- Memoro - Voice recordings and memory management
- Cards - AI-powered flashcard learning
- Storyteller - Creative writing with AI assistance
- Mana - Central account and organization management
App configurations are centralized in apps/web/src/lib/config/apps.ts, defining branding, features, and routing for each application.
Authentication Flow
Web (SvelteKit):
- Server-side authentication using
@supabase/ssr - Middleware in
hooks.server.tshandles session validation - Protected routes in
(app)group require authentication - JWT validation via
safeGetSession()before allowing access
Mobile (Expo):
- Client-side authentication using
@supabase/supabase-js - Custom memory storage for session persistence
AuthProviderinapp/_layout.tsxmanages auth state- Automatic navigation based on authentication status
Database Schema
Key tables:
users- User profiles (linked viaauth_idto Supabase Auth)organizations- Organization entitiesuser_roles- User-organization relationships with rolesteams- Team entities within organizationsteam_members- User-team membershipscredit_transactions- Mana credit transfer history
See CLAUDE.md for detailed architecture documentation.
Testing
Web App
cd apps/web
# Unit tests
pnpm test # Run all tests
pnpm test:ui # Open Vitest UI
# E2E tests
pnpm test:e2e # Run Playwright tests
pnpm test:e2e --ui # Run with Playwright UI
Mobile App
Mobile testing is primarily done through Expo Go or development builds:
cd apps/mobile
npm start # Start dev server
# Then press 'i' for iOS or 'a' for Android
Deployment
Web App
Vercel (Recommended):
cd apps/web
vercel
Netlify:
cd apps/web
netlify deploy
Mobile App
iOS and Android (via EAS):
cd apps/mobile
# Preview build (internal testing)
npm run build:preview
# Production build
npm run build:prod
Configure EAS in eas.json with your build profiles.
Contributing
- Create a feature branch from
main - Make your changes
- Run linting and tests
- Submit a pull request
Code Style
- Use TypeScript for type safety
- Follow ESLint and Prettier configurations
- Write tests for new features
- Use conventional commit messages
Documentation
- CLAUDE.md - Comprehensive developer guide for Claude Code
- apps/web/README.md - Web-specific documentation
- Individual component documentation in source files
Support
For questions or issues, please contact the development team or open an issue in the repository.
License
Private - All rights reserved