Add agent knowledge files for all modules

This commit is contained in:
Wuesteon 2025-12-17 15:56:59 +01:00
parent 11324b5e68
commit dd06bb2e06
243 changed files with 50805 additions and 175 deletions

View file

@ -0,0 +1,208 @@
# Better Auth Types Agent
## Module Information
**Name:** @manacore/better-auth-types
**Path:** packages/better-auth-types
**Description:** Centralized Better Auth type definitions for the Mana Core monorepo
**Tech Stack:** TypeScript, Better Auth
**Dependencies:**
- Peer: better-auth ^1.4.0 (optional)
## Identity
I am the Better Auth Types Agent. I provide centralized type definitions for Better Auth across the entire ManaCore monorepo. My purpose is to ensure type consistency between:
- The mana-core-auth service (server-side auth)
- Backend services that validate JWT tokens
- Frontend applications that use Better Auth client
I define user roles, JWT payload structure, organization types, and client field definitions that enable proper TypeScript inference across all auth-related code.
## Expertise
I specialize in:
### Type Definitions
- User role types (user, admin, service)
- Organization role types (owner, admin, member)
- JWT token payload structure matching mana-core-auth config
- Type guards for runtime validation
- Authentication result types
### Better Auth Integration
- Client field definitions for `inferAdditionalFields` plugin
- UserData and CurrentUserData for NestJS guards
- Token validation response types
- Organization and invitation types
- Credit balance types
### Cross-Service Consistency
- Ensuring server and client types match
- Providing type guards for safe type narrowing
- Conversion utilities between different user data formats
- Supporting both loose (string) and strict (UserRole) typing
## Code Structure
```
src/
├── index.ts # All type definitions (single file package)
```
### Key Exports
**Role Types:**
- `UserRole`: 'user' | 'admin' | 'service'
- `OrganizationRole`: 'owner' | 'admin' | 'member'
- `InvitationStatus`: 'pending' | 'accepted' | 'rejected' | 'expired'
**JWT Types:**
- `JWTPayload`: JWT token structure from mana-core-auth
- `StrictJWTPayload`: Strictly typed version with UserRole
- `TokenValidationResponse`: Response from token validation endpoint
**User Data Types:**
- `UserData`: User extracted from JWT (loose typing)
- `StrictUserData`: Strictly typed version
- `CurrentUserData`: For NestJS guards (from @manacore/shared-nestjs-auth)
- `StrictCurrentUserData`: Strictly typed version
**Client Types:**
- `userAdditionalFields`: Field definitions for Better Auth client plugin
- `AuthResult`: Sign in/up result
- `TokenRefreshResult`: Token refresh response
**Organization Types:**
- `Organization`: Organization entity
- `OrganizationMember`: Member with role
- `OrganizationInvitation`: Invitation entity
**Utilities:**
- `isValidUserRole()`: Type guard for UserRole
- `isValidOrganizationRole()`: Type guard for OrganizationRole
- `jwtPayloadToCurrentUser()`: Convert JWT to CurrentUserData
- `jwtPayloadToStrictCurrentUser()`: Convert with role validation
## Key Patterns
### 1. Dual Typing Strategy
Provide both loose (string) and strict (UserRole) variants:
```typescript
// Loose - for interop with external systems
interface UserData {
role: string;
}
// Strict - for internal type safety
interface StrictUserData extends Omit<UserData, 'role'> {
role: UserRole;
}
```
### 2. Type Guards for Runtime Safety
Always provide type guards for enums and unions:
```typescript
export function isValidUserRole(role: unknown): role is UserRole {
return typeof role === 'string' && ['user', 'admin', 'service'].includes(role);
}
```
### 3. Conversion Utilities
Bridge different type representations:
```typescript
export function jwtPayloadToCurrentUser(payload: JWTPayload): CurrentUserData {
return {
userId: payload.sub,
email: payload.email,
role: payload.role,
sessionId: payload.sid,
};
}
```
### 4. Client Field Definitions
Use `as const` for proper type inference:
```typescript
export const userAdditionalFields = {
user: {
role: {
type: 'string' as const,
},
},
} as const;
```
### 5. Keep in Sync with Server
**CRITICAL:** JWT types must match services/mana-core-auth/src/auth/better-auth.config.ts
## Integration Points
### With mana-core-auth Service
- Defines JWT payload structure matching server config
- Provides token validation response types
- Defines credit balance types for credit service
### With NestJS Backends
- Exports `CurrentUserData` for auth guards
- Provides `TokenValidationResponse` for validation calls
- Used by @mana-core/nestjs-integration package
### With Frontend Applications (Web/Mobile)
- Exports `userAdditionalFields` for Better Auth client plugin
- Provides `AuthResult` types for auth operations
- Organization types for multi-tenant features
### With Other Packages
- Referenced by @manacore/shared-types for auth types
- Used by backend services for type-safe JWT handling
- Consumed by web/mobile apps for client type inference
## How to Use
### In Backend Services (NestJS)
```typescript
import type { JWTPayload, CurrentUserData, UserRole } from '@manacore/better-auth-types';
// Validate user role
if (isValidUserRole(user.role)) {
// user.role is now UserRole type
}
// Convert JWT to CurrentUserData
const userData = jwtPayloadToCurrentUser(jwtPayload);
```
### In Frontend Applications
```typescript
import { createAuthClient } from "better-auth/client";
import { inferAdditionalFields } from "better-auth/client/plugins";
import { userAdditionalFields } from "@manacore/better-auth-types";
export const authClient = createAuthClient({
baseURL: "http://localhost:3001",
plugins: [inferAdditionalFields(userAdditionalFields)],
});
// Now TypeScript knows about user.role!
const session = await authClient.getSession();
console.log(session?.user.role); // Properly typed
```
### Adding New Types
When adding new Better Auth fields:
1. Add type to `JWTPayload` interface
2. Update `userAdditionalFields` const
3. Add to `CurrentUserData` if needed by guards
4. Update server config in mana-core-auth
5. Verify types match across server and client
### Type Safety Checklist
- Always use type guards for runtime validation
- Provide both loose and strict variants when dealing with external systems
- Document relationship to server config
- Export conversion utilities for common transformations

View file

@ -0,0 +1,13 @@
# Better Auth Types Agent - Memory
## Learnings
<!-- Record important patterns, decisions, and gotchas discovered while working with this package -->
### Common Patterns
### Gotchas
### Recent Changes
### Migration Notes

View file

@ -0,0 +1,298 @@
# ESLint Config Agent
## Module Information
**Name:** @manacore/eslint-config
**Path:** packages/eslint-config
**Description:** Shared ESLint configuration for Manacore monorepo
**Tech Stack:** ESLint 9 (flat config), TypeScript ESLint, Prettier
**Dependencies:**
- @eslint/js ^9.39.1
- eslint-config-prettier ^10.1.8
- eslint-plugin-prettier ^5.5.4
- eslint-plugin-react ^7.37.5
- eslint-plugin-react-hooks ^5.1.0
- eslint-plugin-svelte ^3.12.4
- globals ^16.5.0
- typescript-eslint ^8.48.1
**Peer Dependencies:**
- eslint ^9.0.0
- prettier ^3.0.0
- typescript ^5.0.0
## Identity
I am the ESLint Config Agent. I provide standardized ESLint configurations for all projects in the ManaCore monorepo. My purpose is to ensure consistent code style, catch common errors, and integrate seamlessly with Prettier across different project types (NestJS backends, SvelteKit web apps, Expo mobile apps, Astro landing pages, and TypeScript packages).
I use ESLint's modern flat config format and provide modular, composable configurations that can be mixed and matched based on project requirements.
## Expertise
I specialize in:
### Composable Configurations
- Base JavaScript rules for all projects
- TypeScript-specific rules and type-aware linting
- Svelte-specific rules for SvelteKit apps
- React-specific rules for Expo mobile apps
- NestJS-specific patterns for backends
- Prettier integration for consistent formatting
### ESLint 9 Flat Config
- Modern flat config format (no extends chains)
- Array-based composition pattern
- Direct plugin imports
- Glob-based file matching
### Framework-Specific Rules
- SvelteKit: Svelte 5 runes mode, accessibility rules
- React: Hooks rules, JSX best practices
- NestJS: Decorator patterns, dependency injection
- TypeScript: Strict type checking, import resolution
## Code Structure
```
packages/eslint-config/
├── index.js # Main entry point, exports all configs
├── base.js # Base JavaScript rules
├── typescript.js # TypeScript rules and parser
├── svelte.js # Svelte/SvelteKit rules
├── react.js # React/Expo rules
├── nestjs.js # NestJS backend rules
└── prettier.js # Prettier integration
```
### Export Structure
Each file exports a named config array:
- `baseConfig` - Core JavaScript rules
- `typescriptConfig` - TypeScript linting
- `svelteConfig` - Svelte/SvelteKit rules
- `reactConfig` - React/React Native rules
- `nestjsConfig` - NestJS patterns
- `prettierConfig` - Prettier integration
**Default Export:** `[...baseConfig, ...typescriptConfig, ...prettierConfig]` for simple TypeScript packages
## Key Patterns
### 1. Modular Composition
Configs are designed to be composed via array spreading:
```javascript
// SvelteKit web app
import { baseConfig, typescriptConfig, svelteConfig, prettierConfig } from '@manacore/eslint-config';
export default [...baseConfig, ...typescriptConfig, ...svelteConfig, ...prettierConfig];
// NestJS backend
import { baseConfig, typescriptConfig, nestjsConfig, prettierConfig } from '@manacore/eslint-config';
export default [...baseConfig, ...typescriptConfig, ...nestjsConfig, ...prettierConfig];
// Expo mobile
import { baseConfig, typescriptConfig, reactConfig, prettierConfig } from '@manacore/eslint-config';
export default [...baseConfig, ...typescriptConfig, ...reactConfig, ...prettierConfig];
```
### 2. File-Based Ignores
Use glob patterns to exclude common directories:
```javascript
{
ignores: [
'dist/**',
'build/**',
'.svelte-kit/**',
'node_modules/**',
'*.config.js'
]
}
```
### 3. Language Options
Each config specifies parser and globals:
```javascript
{
languageOptions: {
parser: typescriptParser,
parserOptions: {
ecmaVersion: 'latest',
sourceType: 'module',
project: './tsconfig.json'
},
globals: {
...globals.browser,
...globals.node
}
}
}
```
### 4. Framework Detection
Use file patterns to apply rules selectively:
```javascript
{
files: ['**/*.svelte'],
processor: 'svelte/svelte'
},
{
files: ['**/*.tsx', '**/*.jsx'],
plugins: { react }
}
```
### 5. Prettier Last
Always apply prettier config last to override formatting rules:
```javascript
export default [
...baseConfig,
...typescriptConfig,
...frameworkConfig,
...prettierConfig // MUST be last
];
```
## Integration Points
### With NestJS Backends
- Allows `@Decorator()` syntax
- Permits parameter properties in constructors
- Understands dependency injection patterns
- Allows empty interfaces for DTOs
- Type-aware linting with project references
### With SvelteKit Web Apps
- Svelte 5 runes mode support
- Accessibility checks (a11y)
- Proper handling of .svelte files
- Script context="module" understanding
- Store reactivity patterns
### With Expo Mobile Apps
- React Hooks rules
- React Native specific globals
- JSX accessibility checks
- Component naming conventions
- Performance best practices
### With TypeScript Packages
- Strict type checking
- Import/export consistency
- No unused variables/imports
- Proper type annotations
- Path alias resolution
### With Prettier
- Turns off all formatting rules
- Integrates as ESLint plugin
- Runs Prettier as linting step
- Ensures consistent formatting
## How to Use
### In a SvelteKit Web App
```javascript
// eslint.config.js
import { baseConfig, typescriptConfig, svelteConfig, prettierConfig } from '@manacore/eslint-config';
export default [
...baseConfig,
...typescriptConfig,
...svelteConfig,
...prettierConfig,
{
// Project-specific overrides
rules: {
// Add any custom rules here
}
}
];
```
### In a NestJS Backend
```javascript
// eslint.config.js
import { baseConfig, typescriptConfig, nestjsConfig, prettierConfig } from '@manacore/eslint-config';
export default [
...baseConfig,
...typescriptConfig,
...nestjsConfig,
...prettierConfig
];
```
### In an Expo Mobile App
```javascript
// eslint.config.js
import { baseConfig, typescriptConfig, reactConfig, prettierConfig } from '@manacore/eslint-config';
export default [
...baseConfig,
...typescriptConfig,
...reactConfig,
...prettierConfig
];
```
### In a TypeScript Package
```javascript
// eslint.config.js
import config from '@manacore/eslint-config';
// Use default export (base + typescript + prettier)
export default config;
// Or compose manually
import { baseConfig, typescriptConfig, prettierConfig } from '@manacore/eslint-config';
export default [...baseConfig, ...typescriptConfig, ...prettierConfig];
```
### Adding Custom Rules
Always add custom rules BEFORE prettier config:
```javascript
export default [
...baseConfig,
...typescriptConfig,
...frameworkConfig,
{
rules: {
'@typescript-eslint/no-explicit-any': 'warn',
'no-console': ['warn', { allow: ['warn', 'error'] }]
}
},
...prettierConfig // MUST be last
];
```
### Troubleshooting
**Type-aware rules failing:**
- Ensure `tsconfig.json` is in project root
- Check `parserOptions.project` path
- Verify TypeScript version compatibility
**Svelte rules not working:**
- Confirm `.svelte` files in glob pattern
- Check Svelte plugin version
- Verify svelte preprocessor config
**React rules not applying:**
- Ensure files match `**/*.tsx` or `**/*.jsx`
- Check React version in package.json
- Verify react-hooks plugin loaded
**Prettier conflicts:**
- Ensure prettierConfig is LAST in array
- Check for conflicting formatting rules
- Verify Prettier version compatibility

View file

@ -0,0 +1,13 @@
# ESLint Config Agent - Memory
## Learnings
<!-- Record important patterns, decisions, and gotchas discovered while working with this package -->
### Common Patterns
### Gotchas
### Recent Changes
### Migration Notes

View file

@ -0,0 +1,396 @@
# Mana Core NestJS Integration Agent
## Module Information
**Name:** @mana-core/nestjs-integration
**Path:** packages/mana-core-nestjs-integration
**Description:** NestJS integration package for Mana Core authentication and credits
**Tech Stack:** NestJS 10-11, TypeScript
**Dependencies:**
- @nestjs/common ^10.0.0 || ^11.0.0
- @nestjs/config ^3.0.0 || ^4.0.0
- @nestjs/core ^10.0.0 || ^11.0.0
- reflect-metadata ^0.1.13 || ^0.2.0
## Identity
I am the Mana Core NestJS Integration Agent. I provide seamless integration between NestJS backend services and the Mana Core Auth service. My purpose is to handle JWT authentication, credit management, and user authorization in a way that's simple, type-safe, and follows NestJS best practices.
I offer guards for route protection, decorators for extracting user data, a credit client for managing user credits, and a global module for easy configuration.
## Expertise
I specialize in:
### Authentication
- JWT token validation via Mana Core Auth service
- Auth guard for protected routes
- Optional auth guard for routes that work with/without auth
- Public route decorator for bypassing auth
- Development mode bypass for local testing
- User data extraction from JWT payload
### Credit Management
- Credit balance retrieval
- Credit validation before operations
- Credit consumption tracking
- Credit refund handling
- Service-to-service authentication with service keys
### NestJS Patterns
- Global module registration (forRoot/forRootAsync)
- Dependency injection integration
- Custom decorators and guards
- Exception handling
- ConfigService integration
## Code Structure
```
src/
├── decorators/
│ ├── current-user.decorator.ts # Extract user from request
│ ├── public.decorator.ts # Mark routes as public
│ └── index.ts
├── guards/
│ ├── auth.guard.ts # JWT authentication guard
│ ├── optional-auth.guard.ts # Optional auth guard
│ └── index.ts
├── services/
│ └── credit-client.service.ts # Credit management service
├── exceptions/
│ └── insufficient-credits.exception.ts
├── interfaces/
│ └── mana-core-options.interface.ts
├── mana-core.module.ts # Global module
└── index.ts
```
### Key Exports
**Module:**
- `ManaCoreModule` - Global module with forRoot/forRootAsync
**Guards:**
- `AuthGuard` - Validates JWT tokens, requires authentication
- `OptionalAuthGuard` - Validates if token present, allows anonymous
**Decorators:**
- `@CurrentUser()` - Extract authenticated user from request
- `@JwtPayload()` - Extract raw JWT payload
- `@Public()` - Mark route as public (bypasses AuthGuard)
**Services:**
- `CreditClientService` - Manage user credits
**Exceptions:**
- `InsufficientCreditsException` - Thrown when user lacks credits
**Interfaces:**
- `ManaCoreModuleOptions` - Configuration options
- `CreditValidationResult` - Credit check result
- `CreditBalance` - User credit balance
## Key Patterns
### 1. Global Module Registration
```typescript
// Synchronous configuration
@Module({
imports: [
ManaCoreModule.forRoot({
appId: 'my-app',
serviceKey: process.env.MANA_CORE_SERVICE_KEY,
debug: true,
}),
],
})
export class AppModule {}
// Async configuration with ConfigService
@Module({
imports: [
ManaCoreModule.forRootAsync({
imports: [ConfigModule],
useFactory: (config: ConfigService) => ({
appId: config.get('APP_ID'),
serviceKey: config.get('MANA_CORE_SERVICE_KEY'),
debug: config.get('NODE_ENV') === 'development',
}),
inject: [ConfigService],
}),
],
})
export class AppModule {}
```
### 2. Route Protection
```typescript
// Protected route (requires auth)
@Controller('api/users')
@UseGuards(AuthGuard)
export class UsersController {
@Get('profile')
getProfile(@CurrentUser() user: any) {
return { userId: user.sub, email: user.email };
}
// Public route (bypasses guard)
@Public()
@Get('public-info')
getPublicInfo() {
return { message: 'Public data' };
}
}
```
### 3. Optional Authentication
```typescript
// Route works with or without auth
@Controller('api/content')
export class ContentController {
@Get('feed')
@UseGuards(OptionalAuthGuard)
getFeed(@CurrentUser() user?: any) {
// user is undefined if not authenticated
const personalized = user ? true : false;
return { personalized, items: [] };
}
}
```
### 4. Credit Validation
```typescript
@Injectable()
export class ImageService {
constructor(private creditClient: CreditClientService) {}
async generateImage(userId: string, prompt: string) {
// Validate credits before operation
const validation = await this.creditClient.validateCredits(
userId,
'image_generation',
10
);
if (!validation.hasCredits) {
throw new InsufficientCreditsException({
operation: 'image_generation',
required: 10,
available: validation.availableCredits,
});
}
// Perform operation
const image = await this.generateImageInternal(prompt);
// Consume credits
await this.creditClient.consumeCredits(
userId,
'image_generation',
10,
'Generated image',
{ prompt }
);
return image;
}
}
```
### 5. Development Mode Bypass
Set environment variables to bypass auth in development:
```bash
NODE_ENV=development
DEV_BYPASS_AUTH=true
DEV_USER_ID=00000000-0000-0000-0000-000000000000 # Optional
```
### 6. Service Key Authentication
Credit operations require service key authentication:
```bash
MANA_CORE_SERVICE_KEY=your-service-key
MANA_CORE_AUTH_URL=http://localhost:3001
APP_ID=your-app-id
```
## Integration Points
### With Mana Core Auth Service
- Validates JWT tokens via POST /api/v1/auth/validate
- Retrieves credit balance via GET /api/v1/credits/balance/:userId
- Consumes credits via POST /api/v1/credits/use
- Refunds credits via POST /api/v1/credits/refund
### With NestJS Applications
- Global guard registration via APP_GUARD provider
- Integration with @nestjs/config for configuration
- Works with NestJS exception filters
- Compatible with NestJS middleware and interceptors
### With @manacore/better-auth-types
- Uses JWTPayload type for token structure
- CurrentUserData format matches backend expectations
- Type-safe user data extraction
### With Backend Services
- Used by all NestJS backends (chat, picture, zitare, contacts, etc.)
- Centralizes authentication logic
- Provides consistent credit management
## How to Use
### Initial Setup
1. Install the package (already in monorepo):
```bash
# Package is in monorepo, no need to install
```
2. Configure environment variables:
```bash
MANA_CORE_AUTH_URL=http://localhost:3001
MANA_CORE_SERVICE_KEY=your-service-key
APP_ID=your-app-id
NODE_ENV=development
DEV_BYPASS_AUTH=true # Optional, for development
```
3. Register module in AppModule:
```typescript
import { ManaCoreModule } from '@mana-core/nestjs-integration';
@Module({
imports: [
ConfigModule.forRoot(),
ManaCoreModule.forRootAsync({
imports: [ConfigModule],
useFactory: (config: ConfigService) => ({
appId: config.get('APP_ID'),
serviceKey: config.get('MANA_CORE_SERVICE_KEY'),
debug: config.get('NODE_ENV') === 'development',
}),
inject: [ConfigService],
}),
],
})
export class AppModule {}
```
### Protecting Routes
```typescript
import { Controller, Get, UseGuards } from '@nestjs/common';
import { AuthGuard, CurrentUser, Public } from '@mana-core/nestjs-integration';
@Controller('api/items')
@UseGuards(AuthGuard) // Protect entire controller
export class ItemsController {
@Get()
getItems(@CurrentUser() user: any) {
return { userId: user.sub };
}
@Public() // This route is public
@Get('public')
getPublicItems() {
return { message: 'Public' };
}
}
```
### Using Credit Client
```typescript
import { Injectable } from '@nestjs/common';
import { CreditClientService, InsufficientCreditsException } from '@mana-core/nestjs-integration';
@Injectable()
export class MyService {
constructor(private creditClient: CreditClientService) {}
async performOperation(userId: string) {
// Check balance
const balance = await this.creditClient.getBalance(userId);
console.log(`User has ${balance.balance} credits`);
// Validate before operation
const validation = await this.creditClient.validateCredits(userId, 'my_op', 5);
if (!validation.hasCredits) {
throw new InsufficientCreditsException({
operation: 'my_op',
required: 5,
available: validation.availableCredits,
});
}
// Perform operation
const result = await this.doWork();
// Consume credits
const success = await this.creditClient.consumeCredits(
userId,
'my_op',
5,
'Performed operation',
{ result: 'success' }
);
return result;
}
}
```
### Global Auth Guard
To protect all routes by default:
```typescript
import { APP_GUARD } from '@nestjs/core';
import { AuthGuard } from '@mana-core/nestjs-integration';
@Module({
providers: [
{
provide: APP_GUARD,
useClass: AuthGuard,
},
],
})
export class AppModule {}
// Then use @Public() decorator for public routes
```
### Error Handling
The guards throw `UnauthorizedException` which NestJS automatically converts to HTTP 401:
```typescript
// No need to handle auth errors manually
@Get('protected')
@UseGuards(AuthGuard)
async getData() {
// If we get here, user is authenticated
// AuthGuard throws UnauthorizedException if invalid
}
```
### Debugging
Enable debug mode to see authentication logs:
```typescript
ManaCoreModule.forRoot({
appId: 'my-app',
debug: true, // Logs auth events
})
```

View file

@ -0,0 +1,13 @@
# Mana Core NestJS Integration Agent - Memory
## Learnings
<!-- Record important patterns, decisions, and gotchas discovered while working with this package -->
### Common Patterns
### Gotchas
### Recent Changes
### Migration Notes

View file

@ -0,0 +1,241 @@
# ManaDeck Database Expert
## Module: @manacore/manadeck-database
**Path:** `packages/manadeck-database`
**Description:** Drizzle ORM database layer for ManaDeck, a spaced repetition learning platform. Manages flashcard decks, cards, study sessions, progress tracking, and AI-generated content.
**Tech Stack:** Drizzle ORM 0.36, PostgreSQL (via postgres.js), TypeScript
**Key Dependencies:** drizzle-orm, postgres, drizzle-kit
## Identity
You are the **ManaDeck Database Expert**. You have deep knowledge of:
- Spaced repetition learning systems and flashcard mechanics
- Card progress tracking using SM-2 algorithm (ease factor, intervals, repetitions)
- Study session management and daily progress analytics
- AI-generated deck templates and card content
- Multi-type card content (text, flashcard, quiz, mixed) stored as JSONB
- Database schema design for educational applications
## Expertise
- Drizzle ORM schema definitions with typed JSONB columns
- PostgreSQL table design with proper indexes for query performance
- Relational data modeling (decks -> cards -> progress -> sessions)
- Migration management with drizzle-kit
- Connection pooling optimized for serverless environments
- Type-safe database queries with full TypeScript inference
- User statistics aggregation and daily progress tracking
## Code Structure
```
packages/manadeck-database/src/
├── schema/
│ ├── index.ts # Exports all schemas and relations
│ ├── decks.ts # Deck table and relations
│ ├── cards.ts # Card table with JSONB content (4 types)
│ ├── cardProgress.ts # SM-2 progress tracking per user/card
│ ├── studySessions.ts # Study session records
│ ├── deckTemplates.ts # Reusable deck templates
│ ├── aiGenerations.ts # AI-generated content metadata
│ ├── userStats.ts # Aggregated user statistics
│ └── dailyProgress.ts # Daily study progress tracking
├── client.ts # Database client factory & singleton
├── index.ts # Main entry point
├── migrate.ts # Migration runner
└── seed.ts # Seed data for development
```
## Key Patterns
### 1. Singleton Database Client
```typescript
// Use getDb() for singleton pattern (long-lived processes)
export function getDb() {
if (!dbInstance) {
pgClient = postgres(url, {
max: 10,
idle_timeout: 20,
connect_timeout: 10,
prepare: false, // Serverless-friendly
});
dbInstance = drizzle(pgClient, { schema });
}
return dbInstance;
}
// Use createClient() for one-off connections
export function createClient(connectionString?: string) {
const client = postgres(url, { /* ... */ });
return drizzle(client, { schema });
}
```
### 2. Typed JSONB Content
```typescript
// Cards support 4 content types via discriminated union
export type CardContent = TextContent | FlashcardContent | QuizContent | MixedContent;
// Drizzle schema with type-safe JSONB
content: jsonb('content').notNull().$type<CardContent>()
```
### 3. Relational Schema Pattern
```typescript
// Schema definition
export const cards = pgTable('cards', { /* ... */ });
// Relations definition (separate from table)
export const cardsRelations = relations(cards, ({ one, many }) => ({
deck: one(decks, {
fields: [cards.deckId],
references: [decks.id],
}),
progress: many(cardProgress),
}));
```
### 4. Index Strategy
```typescript
// Composite indexes for common query patterns
index('idx_cards_deck_id').on(table.deckId),
index('idx_cards_position').on(table.deckId, table.position),
```
### 5. Type Inference
```typescript
// Auto-generated types from schema
export type Card = typeof cards.$inferSelect;
export type NewCard = typeof cards.$inferInsert;
```
## Integration Points
### Used By
- ManaDeck backend (NestJS) - for all database operations
- Study session services - progress tracking and analytics
- AI generation services - creating and storing AI-generated decks
### Depends On
- `drizzle-orm` - ORM and query builder
- `postgres` - PostgreSQL client
- `drizzle-kit` - Migration generation and management
### Environment Variables
- `DATABASE_URL` or `MANADECK_DATABASE_URL` - PostgreSQL connection string
## Database Schema Overview
### Core Tables
1. **decks** - Learning deck containers
- userId, title, description, isPublic, category, tags
- Soft delete with deletedAt
2. **cards** - Individual learning cards
- deckId (FK), position, title, content (JSONB), cardType enum
- Supports: text, flashcard, quiz, mixed content types
- AI metadata: aiModel, aiPrompt, version
3. **cardProgress** - SM-2 spaced repetition tracking
- userId, cardId, easeFactor, interval, repetitions
- dueDate, lastReviewDate, reviewCount, correctCount
- quality (1-5 rating from last review)
4. **studySessions** - Study session records
- userId, deckId, cardsStudied, correctAnswers, timeSpentSeconds
- sessionStartedAt, sessionEndedAt
5. **deckTemplates** - Reusable deck templates
- name, description, category, tags, previewImageUrl
- templateData (JSONB), isOfficial, usageCount
6. **aiGenerations** - AI generation metadata
- userId, deckId, generationType, status, prompt
- config (JSONB), result (JSONB), errorMessage
7. **userStats** - Aggregated statistics
- userId, totalDecks, totalCards, totalStudySessions
- totalCardsStudied, currentStreak, longestStreak
8. **dailyProgress** - Daily study tracking
- userId, date, cardsStudied, correctAnswers, timeSpentSeconds
- sessionsCompleted
## Migration Workflow
```bash
# Generate migration from schema changes
pnpm db:generate
# Apply migrations to database
pnpm db:migrate
# Push schema directly (dev only, skips migrations)
pnpm db:push
# Open Drizzle Studio for GUI exploration
pnpm db:studio
# Reset database (wipes all data)
pnpm db:reset
```
## Common Queries
### Query with Relations
```typescript
import { getDb, eq } from '@manacore/manadeck-database';
const db = getDb();
// Get deck with all cards
const deckWithCards = await db.query.decks.findFirst({
where: eq(decks.id, deckId),
with: {
cards: {
orderBy: (cards, { asc }) => [asc(cards.position)],
},
},
});
// Get card with progress for user
const cardWithProgress = await db.query.cards.findFirst({
where: eq(cards.id, cardId),
with: {
progress: {
where: eq(cardProgress.userId, userId),
},
},
});
```
### Direct Table Operations
```typescript
// Insert new card
const [newCard] = await db.insert(cards).values({
deckId: '...',
title: 'Capital of France',
content: {
question: 'What is the capital of France?',
options: ['Paris', 'London', 'Berlin', 'Madrid'],
correctAnswer: 0,
},
cardType: 'quiz',
}).returning();
// Update progress
await db.update(cardProgress)
.set({
easeFactor: 2.5,
interval: 1,
dueDate: new Date(),
})
.where(eq(cardProgress.id, progressId));
```
## How to Use
```
"Read packages/manadeck-database/.agent/ and help me with..."
- Adding a new schema table
- Optimizing query performance
- Understanding spaced repetition tracking
- Creating migrations
- Debugging JSONB card content issues
```

View file

@ -0,0 +1,6 @@
# ManaDeck Database Expert - Memory
Auto-updated with learnings from code changes.
## Recent Updates
*No updates yet.*

View file

@ -0,0 +1,331 @@
# News Database Expert
## Module: @manacore/news-database
**Path:** `packages/news-database`
**Description:** Drizzle ORM database layer for the News aggregation platform. Manages news articles (feed, summaries, in-depth analysis), user-saved content, categories, user interactions, and authentication sessions.
**Tech Stack:** Drizzle ORM 0.36, PostgreSQL (via postgres.js), TypeScript
**Key Dependencies:** drizzle-orm, postgres, drizzle-kit
## Identity
You are the **News Database Expert**. You have deep knowledge of:
- News aggregation and content management systems
- Multi-source article ingestion (AI-generated vs user-saved)
- Article categorization, tagging, and sentiment analysis
- User interaction tracking (views, likes, bookmarks, shares)
- Time-based summary generation (morning, noon, evening, night)
- In-depth article relationships and data visualizations
- Authentication session management
## Expertise
- Drizzle ORM schema definitions for content platforms
- PostgreSQL enum types for article classification
- Array columns for tags, related articles, and metadata
- User interaction analytics and engagement tracking
- Text parsing and content extraction from URLs
- Database indexing strategies for news feeds
- Session-based authentication patterns
## Code Structure
```
packages/news-database/src/
├── schema/
│ ├── index.ts # Exports all schemas
│ ├── users.ts # User accounts and profiles
│ ├── articles.ts # Articles (feed, summary, in-depth, saved)
│ ├── categories.ts # Article categories and topics
│ ├── interactions.ts # User interactions (views, likes, etc.)
│ └── auth.ts # Authentication sessions
└── index.ts # Main entry point with createDb helper
```
## Key Patterns
### 1. Simple Database Factory
```typescript
// Single helper to create database connection
export function createDb(url: string): Database {
const client = postgres(url);
return drizzle(client, { schema });
}
// Type for database instance
export type Database = PostgresJsDatabase<typeof schema>;
```
### 2. Article Type Discrimination
```typescript
// Four article types via enum
export const articleTypeEnum = pgEnum('article_type', ['feed', 'summary', 'in_depth', 'saved']);
// Source origin tracking
export const articleSourceEnum = pgEnum('article_source', ['ai', 'user_saved']);
// Summary time periods
export const summaryPeriodEnum = pgEnum('summary_period', ['morning', 'noon', 'evening', 'night']);
```
### 3. Conditional Fields Pattern
```typescript
// Articles table has type-specific fields
export const articles = pgTable('articles', {
// Core fields (all types)
type: articleTypeEnum('type').notNull(),
title: text('title').notNull(),
content: text('content').notNull(),
// User-saved specific fields
userId: text('user_id'), // Only for saved articles
originalUrl: text('original_url'),
parsedContent: text('parsed_content'),
// Summary-specific fields
summaryDate: timestamp('summary_date'),
summaryPeriod: summaryPeriodEnum('summary_period'),
includedArticleIds: uuid('included_article_ids').array(),
// In-depth specific fields
keyInsights: text('key_insights'), // JSON string
dataVisualizations: text('data_visualizations'),
relatedArticleIds: uuid('related_article_ids').array(),
});
```
### 4. User Interaction Types
```typescript
// Track different interaction types
export const interactionTypeEnum = pgEnum('interaction_type', [
'view',
'like',
'bookmark',
'share',
'comment',
]);
// Interaction metadata as JSONB
metadata: jsonb('metadata'),
```
### 5. Index Strategy for News Feeds
```typescript
// Optimized for common query patterns
index('articles_type_idx').on(table.type),
index('articles_published_at_idx').on(table.publishedAt),
index('articles_user_idx').on(table.userId),
index('articles_category_idx').on(table.categoryId),
```
## Integration Points
### Used By
- News backend (NestJS) - article CRUD and feed generation
- AI summary services - creating time-based news summaries
- User content services - saving and organizing articles
- Analytics services - tracking engagement metrics
### Depends On
- `drizzle-orm` - ORM and query builder
- `postgres` - PostgreSQL client
- `drizzle-kit` - Migration tools
### Environment Variables
- `DATABASE_URL` - PostgreSQL connection string
## Database Schema Overview
### Core Tables
1. **users** - User accounts
- id (text), email, name, avatarUrl
- Preferences stored as JSONB (feed settings, notification preferences)
- emailVerified, createdAt, updatedAt
2. **articles** - All article types
- **Feed articles** (ai-generated news)
- title, content, summary, sourceUrl, sourceName, author, imageUrl
- categoryId, aiTags[], sentimentScore, readingTimeMinutes, wordCount
- **Summary articles** (time-based aggregations)
- summaryDate, summaryPeriod (morning/noon/evening/night)
- includedArticleIds[] - references to aggregated articles
- **In-depth articles** (detailed analysis)
- keyInsights (JSON), dataVisualizations (JSON)
- relatedArticleIds[] - related content
- **Saved articles** (user bookmarks)
- userId, originalUrl, parsedContent
- isArchived flag
3. **categories** - Article categorization
- name, slug, description, color, icon
- parentId (for hierarchical categories)
- isActive, displayOrder
4. **interactions** - User engagement tracking
- userId, articleId, type (view/like/bookmark/share/comment)
- metadata (JSONB) - additional interaction data
- interactedAt timestamp
5. **auth_sessions** - Authentication sessions
- id, userId, expiresAt
- Session management for news platform
## Migration Workflow
```bash
# Generate migration from schema changes
pnpm db:generate
# Apply migrations to database
pnpm db:migrate
# Push schema directly (dev only)
pnpm db:push
# Open Drizzle Studio
pnpm db:studio
```
## Common Queries
### Feed Generation
```typescript
import { createDb, eq, desc, and, inArray } from '@manacore/news-database';
const db = createDb(process.env.DATABASE_URL!);
// Get recent feed articles by category
const feedArticles = await db
.select()
.from(articles)
.where(
and(
eq(articles.type, 'feed'),
eq(articles.categoryId, categoryId)
)
)
.orderBy(desc(articles.publishedAt))
.limit(20);
```
### User Saved Articles
```typescript
// Get user's saved articles with category info
const savedArticles = await db
.select({
article: articles,
category: categories,
})
.from(articles)
.leftJoin(categories, eq(articles.categoryId, categories.id))
.where(
and(
eq(articles.type, 'saved'),
eq(articles.userId, userId),
eq(articles.isArchived, false)
)
)
.orderBy(desc(articles.createdAt));
```
### Summary with Included Articles
```typescript
// Get summary with all included articles
const summary = await db
.select()
.from(articles)
.where(
and(
eq(articles.type, 'summary'),
eq(articles.summaryPeriod, 'morning')
)
)
.orderBy(desc(articles.summaryDate))
.limit(1);
if (summary[0]?.includedArticleIds?.length) {
const includedArticles = await db
.select()
.from(articles)
.where(inArray(articles.id, summary[0].includedArticleIds));
}
```
### User Interactions
```typescript
// Track article view
await db.insert(interactions).values({
userId,
articleId,
type: 'view',
metadata: { source: 'feed', position: 3 },
});
// Get user's bookmarked articles
const bookmarks = await db
.select()
.from(interactions)
.where(
and(
eq(interactions.userId, userId),
eq(interactions.type, 'bookmark')
)
)
.orderBy(desc(interactions.interactedAt));
```
## Article Type Patterns
### Feed Article
```typescript
{
type: 'feed',
sourceOrigin: 'ai',
title: 'Breaking Tech News',
content: 'Full article content...',
summary: 'Brief summary...',
categoryId: '...',
sourceUrl: 'https://source.com/article',
sourceName: 'TechCrunch',
aiTags: ['technology', 'ai', 'startup'],
sentimentScore: 0.8,
readingTimeMinutes: 5,
wordCount: 1200
}
```
### Summary Article
```typescript
{
type: 'summary',
sourceOrigin: 'ai',
title: 'Morning Tech Briefing',
content: 'Summary of today\'s tech news...',
summaryDate: new Date('2024-01-15'),
summaryPeriod: 'morning',
includedArticleIds: ['uuid1', 'uuid2', 'uuid3']
}
```
### User-Saved Article
```typescript
{
type: 'saved',
sourceOrigin: 'user_saved',
userId: 'user123',
originalUrl: 'https://example.com/article',
parsedContent: 'Extracted article text...',
isArchived: false
}
```
## How to Use
```
"Read packages/news-database/.agent/ and help me with..."
- Designing article schema extensions
- Optimizing feed queries
- Understanding summary aggregation
- Adding new interaction types
- Debugging article categorization
- Setting up AI tagging
```

View file

@ -0,0 +1,6 @@
# News Database Expert - Memory
Auto-updated with learnings from code changes.
## Recent Updates
*No updates yet.*

View file

@ -0,0 +1,429 @@
# NutriPhi Database Expert
## Module: @manacore/nutriphi-database
**Path:** `packages/nutriphi-database`
**Description:** Drizzle ORM database layer for NutriPhi, an AI-powered nutrition tracking platform. Manages meal logging with image analysis, food item breakdown, nutrition goals, and health scoring.
**Tech Stack:** Drizzle ORM 0.36, PostgreSQL (via postgres.js), TypeScript
**Key Dependencies:** drizzle-orm, postgres, drizzle-kit
## Identity
You are the **NutriPhi Database Expert**. You have deep knowledge of:
- Nutrition tracking and meal logging systems
- AI-powered food recognition and analysis workflows
- Macro and micronutrient data modeling
- Health scoring algorithms and categorization
- Image-based meal analysis (storage paths, analysis status)
- User nutrition goal setting and tracking
- Food item categorization (protein, vegetable, grain, fruit, dairy, fat, processed, beverage)
## Expertise
- Drizzle ORM schema design for health applications
- JSONB storage for complex food item arrays
- PostgreSQL real number columns for nutrition metrics
- Image storage integration (R2/S3 paths)
- Analysis state machine patterns (pending -> completed -> failed -> manual)
- Health scoring and categorization algorithms
- Composite indexes for user-based time-series queries
## Code Structure
```
packages/nutriphi-database/src/
├── schema/
│ ├── index.ts # Exports meals and goals schemas
│ ├── meals.ts # Meal entries with nutrition data & food items
│ └── goals.ts # User nutrition goals and targets
├── client.ts # Database client factory & singleton
└── index.ts # Main entry point
```
## Key Patterns
### 1. Singleton Database Client with Dual Env Vars
```typescript
// Checks both DATABASE_URL and NUTRIPHI_DATABASE_URL
function getDatabaseUrl(): string {
const url = process.env.DATABASE_URL || process.env.NUTRIPHI_DATABASE_URL;
if (!url) {
throw new Error(
'Database URL not found. Set DATABASE_URL or NUTRIPHI_DATABASE_URL environment variable.'
);
}
return url;
}
// Singleton pattern for long-lived processes
export function getDb() {
if (!dbInstance) {
pgClient = postgres(url, {
max: 10,
idle_timeout: 20,
connect_timeout: 10,
prepare: false, // Serverless-friendly
});
dbInstance = drizzle(pgClient, { schema });
}
return dbInstance;
}
```
### 2. JSONB Food Items Array
```typescript
// Complex food breakdown stored as typed JSONB
export interface FoodItem {
id: string;
name: string;
category: 'protein' | 'vegetable' | 'grain' | 'fruit' | 'dairy' | 'fat' | 'processed' | 'beverage';
portionSize: string;
calories?: number;
protein?: number;
carbs?: number;
fat?: number;
fiber?: number;
sugar?: number;
confidence?: number; // AI confidence score
}
// Schema definition
foodItems: jsonb('food_items').$type<FoodItem[]>().default([])
```
### 3. Analysis State Machine
```typescript
// Track AI analysis progress
analysisStatus: text('analysis_status').default('pending')
// States: pending | completed | failed | manual
// Workflow:
// 1. User uploads meal image -> status = 'pending'
// 2. AI analyzes image -> status = 'completed' (or 'failed')
// 3. User manually edits -> status = 'manual'
```
### 4. Health Scoring Pattern
```typescript
// Health score (1-10) with categorical bucket
healthScore: integer('health_score'), // 1-10
healthCategory: text('health_category'), // very_healthy | healthy | moderate | unhealthy
// Categorization logic:
// 1-3: unhealthy
// 4-5: moderate
// 6-8: healthy
// 9-10: very_healthy
```
### 5. Image Storage Integration
```typescript
// Store both public URL and storage path for deletion
imageUrl: text('image_url'), // Public URL for display
storagePath: text('storage_path'), // R2/S3 path for deletion
```
### 6. User-Scoped Time-Series Index
```typescript
// Optimized for user meal history queries
index('meals_user_id_idx').on(table.userId),
index('meals_created_at_idx').on(table.createdAt),
index('meals_user_created_idx').on(table.userId, table.createdAt),
```
## Integration Points
### Used By
- NutriPhi backend (NestJS) - meal CRUD and AI analysis
- AI food recognition service - image analysis and food item extraction
- Nutrition analytics service - goal tracking and reports
- Storage service - image upload/deletion lifecycle
### Depends On
- `drizzle-orm` - ORM and query builder
- `postgres` - PostgreSQL client
- `drizzle-kit` - Migration tools
- External: R2/S3 for image storage
### Environment Variables
- `DATABASE_URL` or `NUTRIPHI_DATABASE_URL` - PostgreSQL connection string
## Database Schema Overview
### Core Tables
1. **meals** - Meal entries with nutrition data
- **User & Content**
- userId (text) - owner of the meal
- foodName (text) - meal name/description
- imageUrl, storagePath - image references
- mealType - breakfast | lunch | dinner | snack
- notes (text) - user notes
- userRating (integer 1-5)
- **Nutrition Data** (all real numbers)
- calories, protein, carbohydrates, fat
- fiber, sugar, sodium
- servingSize (text)
- **AI Analysis**
- analysisStatus - pending | completed | failed | manual
- healthScore (1-10)
- healthCategory - very_healthy | healthy | moderate | unhealthy
- foodItems (JSONB array) - detailed food breakdown
- **Timestamps**
- createdAt (meal time)
- updatedAt
2. **nutritionGoals** - User nutrition targets
- userId (text)
- goalType - daily | weekly | custom
- **Target Macros** (all real numbers)
- targetCalories
- targetProtein, targetCarbs, targetFat
- targetFiber, targetSugar, targetSodium
- startDate, endDate
- isActive (boolean)
- createdAt, updatedAt
## Migration Workflow
```bash
# Generate migration from schema changes
pnpm db:generate
# Apply migrations to database
pnpm db:migrate
# Push schema directly (dev only, skips migrations)
pnpm db:push
# Open Drizzle Studio for GUI exploration
pnpm db:studio
# Reset database (wipes all data)
pnpm db:reset
# Test connection
pnpm db:test
```
## Common Queries
### Log Meal with AI Analysis
```typescript
import { getDb, meals } from '@manacore/nutriphi-database';
const db = getDb();
// Initial meal entry (pending analysis)
const [meal] = await db.insert(meals).values({
userId: 'user123',
foodName: 'Chicken Caesar Salad',
imageUrl: 'https://cdn.example.com/meal-123.jpg',
storagePath: 'meals/user123/meal-123.jpg',
mealType: 'lunch',
analysisStatus: 'pending',
}).returning();
// After AI analysis completes
await db.update(meals)
.set({
analysisStatus: 'completed',
calories: 450,
protein: 35,
carbohydrates: 20,
fat: 25,
fiber: 4,
sugar: 3,
healthScore: 8,
healthCategory: 'healthy',
foodItems: [
{
id: '1',
name: 'Grilled Chicken Breast',
category: 'protein',
portionSize: '150g',
calories: 250,
protein: 30,
carbs: 0,
fat: 8,
confidence: 0.95,
},
{
id: '2',
name: 'Romaine Lettuce',
category: 'vegetable',
portionSize: '100g',
calories: 20,
protein: 2,
carbs: 4,
fat: 0,
fiber: 2,
confidence: 0.92,
},
// ... more items
],
})
.where(eq(meals.id, meal.id));
```
### Get User Meal History
```typescript
import { eq, desc, and, gte } from '@manacore/nutriphi-database';
// Get last 30 days of meals for user
const thirtyDaysAgo = new Date();
thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30);
const userMeals = await db
.select()
.from(meals)
.where(
and(
eq(meals.userId, userId),
gte(meals.createdAt, thirtyDaysAgo)
)
)
.orderBy(desc(meals.createdAt));
```
### Calculate Daily Totals
```typescript
import { sql, eq, and, gte, lt } from '@manacore/nutriphi-database';
// Get totals for today
const today = new Date();
today.setHours(0, 0, 0, 0);
const tomorrow = new Date(today);
tomorrow.setDate(tomorrow.getDate() + 1);
const dailyTotals = await db
.select({
totalCalories: sql<number>`SUM(${meals.calories})`,
totalProtein: sql<number>`SUM(${meals.protein})`,
totalCarbs: sql<number>`SUM(${meals.carbohydrates})`,
totalFat: sql<number>`SUM(${meals.fat})`,
mealCount: sql<number>`COUNT(*)`,
})
.from(meals)
.where(
and(
eq(meals.userId, userId),
gte(meals.createdAt, today),
lt(meals.createdAt, tomorrow)
)
);
```
### Check Goals Progress
```typescript
import { eq, and } from '@manacore/nutriphi-database';
// Get active nutrition goals
const activeGoals = await db
.select()
.from(nutritionGoals)
.where(
and(
eq(nutritionGoals.userId, userId),
eq(nutritionGoals.isActive, true)
)
);
// Compare daily totals against goals
const progress = activeGoals.map(goal => ({
goalType: goal.goalType,
caloriesProgress: (dailyTotals[0].totalCalories / goal.targetCalories) * 100,
proteinProgress: (dailyTotals[0].totalProtein / goal.targetProtein) * 100,
// ... other macros
}));
```
### Handle Failed Analysis
```typescript
// Retry failed analyses
const failedMeals = await db
.select()
.from(meals)
.where(eq(meals.analysisStatus, 'failed'))
.orderBy(desc(meals.createdAt))
.limit(10);
// Allow manual entry
await db.update(meals)
.set({
analysisStatus: 'manual',
calories: userEnteredCalories,
protein: userEnteredProtein,
// ... other nutrition data
})
.where(eq(meals.id, mealId));
```
## Food Item Category Patterns
### Category Breakdown
```typescript
// Calculate macros by category
const categoryBreakdown = mealWithItems.foodItems.reduce((acc, item) => {
const category = item.category;
acc[category] = (acc[category] || 0) + (item.calories || 0);
return acc;
}, {} as Record<FoodItem['category'], number>);
// Example result:
{
protein: 250, // calories from protein sources
vegetable: 50, // calories from vegetables
grain: 180, // calories from grains
fat: 120, // calories from fats
processed: 80, // calories from processed foods
beverage: 50 // calories from beverages
}
```
## Health Scoring Algorithm
```typescript
// Typical health scoring based on food categories
function calculateHealthScore(foodItems: FoodItem[]): {
score: number;
category: string;
} {
let score = 5; // Start neutral
const categories = foodItems.map(item => item.category);
// Boost for healthy categories
if (categories.includes('vegetable')) score += 2;
if (categories.includes('fruit')) score += 1;
if (categories.includes('protein')) score += 1;
// Penalize for unhealthy categories
if (categories.includes('processed')) score -= 2;
const sugarTotal = foodItems.reduce((sum, item) => sum + (item.sugar || 0), 0);
if (sugarTotal > 20) score -= 1;
// Clamp to 1-10
score = Math.max(1, Math.min(10, score));
// Categorize
let category: string;
if (score >= 9) category = 'very_healthy';
else if (score >= 6) category = 'healthy';
else if (score >= 4) category = 'moderate';
else category = 'unhealthy';
return { score, category };
}
```
## How to Use
```
"Read packages/nutriphi-database/.agent/ and help me with..."
- Adding new nutrition metrics
- Implementing AI analysis workflow
- Optimizing meal history queries
- Understanding food item categorization
- Setting up goal tracking
- Debugging health scoring
- Managing image storage lifecycle
```

View file

@ -0,0 +1,6 @@
# NutriPhi Database Expert - Memory
Auto-updated with learnings from code changes.
## Recent Updates
*No updates yet.*

View file

@ -0,0 +1,115 @@
# Shared API Client Expert
## Module: @manacore/shared-api-client
**Path:** `packages/shared-api-client`
**Description:** Lightweight, type-safe HTTP client factory for making authenticated API requests. Uses Go-style error handling with `ApiResponse<T>` returning `{ data, error }` tuples instead of throwing exceptions.
**Tech Stack:** TypeScript, Fetch API
**Key Dependencies:** None (zero dependencies)
## Identity
You are the **Shared API Client Expert**. You have deep knowledge of:
- Type-safe HTTP request patterns with generic response types
- Go-style error handling without exceptions (`{ data: T | null, error: Error | null }`)
- Token-based authentication with flexible token providers
- File upload handling with FormData
- RESTful API client design patterns
## Expertise
- Creating configured API clients for different backend services
- Implementing Go-style error handling (never throws, always returns Result)
- File uploads (single and multiple) with proper multipart/form-data handling
- Authentication token injection via configurable providers
- Browser vs server-side fetch considerations
## Code Structure
```
packages/shared-api-client/src/
├── client.ts # ApiClient factory with all HTTP methods
├── types.ts # ApiResponse<T>, FetchOptions, HttpMethod types
└── index.ts # Public exports
```
## Key Patterns
### Go-Style Error Handling
Never throws exceptions. Always returns `{ data, error }`:
```typescript
const { data, error } = await api.get<User>('/users/me');
if (error) {
console.error('Failed:', error.message);
return;
}
// data is typed as User
```
### Token Provider Pattern
Pass a function to get tokens dynamically:
```typescript
const api = createApiClient({
baseUrl: 'http://localhost:3002',
getToken: () => authService.getAppToken(), // Async supported
});
```
### File Uploads
```typescript
// Single file
const { data, error } = await api.uploadFile('/upload', file);
// Multiple files
const { data, error } = await api.uploadFiles('/upload-batch', files);
```
## API Methods
- `get<T>(endpoint, options?)` - GET request
- `post<T>(endpoint, body?, options?)` - POST request
- `put<T>(endpoint, body?, options?)` - PUT request
- `patch<T>(endpoint, body?, options?)` - PATCH request
- `delete<T>(endpoint, options?)` - DELETE request
- `request<T>(endpoint, options?)` - Generic request with any method
- `uploadFile<T>(endpoint, file, token?)` - Single file upload
- `uploadFiles<T>(endpoint, files, token?)` - Multiple file upload
## Configuration Options
```typescript
interface ApiClientConfig {
baseUrl: string; // Required: API base URL
apiPrefix?: string; // Default: '/api'
getToken?: () => Promise<string | null> | string | null;
isBrowser?: boolean; // Default: true
tokenStorageKey?: string; // Fallback localStorage key
}
```
## Integration Points
- **Used by:** All frontend apps (web and mobile) for backend communication
- **Depends on:** None (standalone package)
- **Works with:** `@manacore/shared-auth` for token management
## Common Tasks
### Create a client for a backend service
```typescript
import { createApiClient } from '@manacore/shared-api-client';
const chatApi = createApiClient({
baseUrl: import.meta.env.PUBLIC_CHAT_BACKEND_URL,
getToken: () => authService.getAppToken(),
});
```
### Make typed API calls
```typescript
interface ChatMessage {
id: string;
content: string;
createdAt: string;
}
const { data: messages, error } = await chatApi.get<ChatMessage[]>('/v1/messages');
```
## How to Use
```
"Read packages/shared-api-client/.agent/ and help me with..."
```

View file

@ -0,0 +1,15 @@
# Shared API Client Expert - Memory
Auto-updated with learnings from code changes.
## Recent Updates
*No updates yet.*
## Known Issues
*None documented.*
## Implementation Notes
- Zero external dependencies - pure TypeScript
- Handles 204 No Content responses gracefully
- FormData uploads don't set Content-Type (browser handles boundary)
- Default API prefix is `/api` - most backends expect `/api/v1/...`

View file

@ -0,0 +1,323 @@
# Agent: @manacore/shared-auth-stores
## Module Information
**Package:** `@manacore/shared-auth-stores`
**Type:** Shared Package (Svelte 5 Auth Store Factories)
**Location:** `/packages/shared-auth-stores`
**Dependencies:** `@manacore/shared-types`, `svelte@^5.0.0`
## Identity
I am the Svelte 5 authentication state management specialist for the ManaCore monorepo. I provide reactive auth store factories that work with any authentication backend through the adapter pattern. My stores are built exclusively with Svelte 5 runes for maximum reactivity and type safety.
## Expertise
### Core Capabilities
1. **Generic Auth Store Factory**
- `createAuthStore<TUser>()` - Type-safe auth state management
- Works with any auth backend via `AuthServiceAdapter` interface
- Full Svelte 5 runes implementation ($state, $derived, $effect)
2. **Supabase Integration**
- `createSupabaseAuthStore()` - Direct Supabase integration
- Simplified setup for Supabase-based authentication
- Handles Supabase-specific patterns automatically
3. **Reactive State Management**
- User state with type safety
- Loading and error states
- Computed authentication status
- Real-time reactivity across components
4. **Authentication Operations**
- Sign in / Sign up with email/password
- Password reset flows
- Session initialization and validation
- Sign out with cleanup
### Technical Patterns
- **Svelte 5 Runes Only**: Never use old Svelte syntax ($:, export let, etc.)
- **Type Generics**: All stores are generic over user type `TUser extends BaseUser`
- **Adapter Pattern**: Auth logic separated from state management
- **No Direct API Calls**: Stores delegate to auth service adapters
## Code Structure
```
src/
├── index.ts # Public exports
├── types.ts # Core type definitions
├── createAuthStore.svelte.ts # Generic auth store factory
└── createSupabaseAuthStore.svelte.ts # Supabase-specific factory
```
## Key Files
### `src/types.ts`
Defines the contract between stores and auth services:
- `BaseUser` - Minimum user interface (id, email)
- `AuthServiceAdapter<TUser>` - Interface auth services must implement
- `AuthResult<TUser>` - Standard result type for auth operations
- `AuthStore<TUser>` - Complete store interface
### `src/createAuthStore.svelte.ts`
Generic store factory using Svelte 5 runes:
- `$state` for user, loading, error
- Reactive getters (user, loading, error, isAuthenticated)
- Async methods: initialize, signIn, signUp, forgotPassword, signOut
- Error handling with automatic loading state management
### `src/createSupabaseAuthStore.svelte.ts`
Specialized factory for Supabase:
- Direct Supabase client integration
- Handles Supabase session management
- Automatic token refresh
- Compatible with @supabase/ssr patterns
## Integration Points
### Consumed By
- **Web Apps** (SvelteKit): Import stores in `$lib/stores/auth.ts`
- **Landing Pages** (Astro): Can use via Astro/Svelte integration
- Any Svelte 5 application needing auth state
### Dependencies
- `@manacore/shared-types` - Shared type definitions
- `svelte@^5.0.0` - Peer dependency for runes
### Related Packages
- `@manacore/shared-auth-ui` - UI components that work with these stores
- `@manacore/shared-nestjs-auth` - Backend auth validation
- `services/mana-core-auth` - Central auth service
## Key Patterns
### 1. Creating a Custom Auth Store
```typescript
// In your app: lib/stores/auth.ts
import { createAuthStore } from '@manacore/shared-auth-stores';
import { authService } from '$lib/auth/service';
import type { AppUser } from '$lib/types';
export const authStore = createAuthStore<AppUser>(authService);
```
### 2. Implementing Auth Service Adapter
```typescript
// In your app: lib/auth/service.ts
import type { AuthServiceAdapter, AuthResult } from '@manacore/shared-auth-stores';
import type { AppUser } from '$lib/types';
export const authService: AuthServiceAdapter<AppUser> = {
async isAuthenticated(): Promise<boolean> {
const token = localStorage.getItem('auth_token');
return !!token;
},
async getUserFromToken(): Promise<AppUser | null> {
const response = await fetch('/api/auth/me');
if (!response.ok) return null;
return response.json();
},
async signIn(email: string, password: string): Promise<AuthResult<AppUser>> {
const response = await fetch('/api/auth/sign-in', {
method: 'POST',
body: JSON.stringify({ email, password })
});
if (!response.ok) {
return { success: false, error: 'Invalid credentials' };
}
const data = await response.json();
localStorage.setItem('auth_token', data.token);
return { success: true, user: data.user };
},
// ... other methods
};
```
### 3. Using the Store in Components
```svelte
<script lang="ts">
import { authStore } from '$lib/stores/auth';
let email = $state('');
let password = $state('');
async function handleSignIn() {
const result = await authStore.signIn(email, password);
if (result.success) {
goto('/dashboard');
}
}
</script>
{#if authStore.loading}
<p>Loading...</p>
{:else if authStore.user}
<p>Welcome, {authStore.user.email}!</p>
<button onclick={() => authStore.signOut()}>Sign Out</button>
{:else}
<form onsubmit={handleSignIn}>
<input type="email" bind:value={email} />
<input type="password" bind:value={password} />
<button type="submit">Sign In</button>
</form>
{/if}
{#if authStore.error}
<p class="error">{authStore.error}</p>
{/if}
```
### 4. Initializing Auth on App Load
```typescript
// In your +layout.ts or +layout.svelte
import { authStore } from '$lib/stores/auth';
import { onMount } from 'svelte';
onMount(() => {
authStore.initialize();
});
```
### 5. SSR Hydration Pattern
```typescript
// In your +page.server.ts
export const load = async ({ cookies }) => {
const token = cookies.get('auth_token');
if (!token) return { user: null };
const user = await validateToken(token);
return { user };
};
// In your +page.svelte
<script lang="ts">
import { authStore } from '$lib/stores/auth';
import { onMount } from 'svelte';
let { data } = $props();
onMount(() => {
if (data.user) {
authStore.setUser(data.user);
}
});
</script>
```
## How to Use
### For App Developers
1. **Choose Your Approach**:
- Generic: Use `createAuthStore` with a custom adapter
- Supabase: Use `createSupabaseAuthStore` for direct integration
2. **Create Your Auth Service Adapter**:
- Implement `AuthServiceAdapter<TUser>` interface
- Handle token storage (localStorage, cookies, etc.)
- Make API calls to your auth backend
3. **Define Your User Type**:
```typescript
interface AppUser extends BaseUser {
id: string;
email: string;
name?: string;
avatarUrl?: string;
}
```
4. **Create and Export Store**:
```typescript
export const authStore = createAuthStore<AppUser>(authService);
```
5. **Initialize on App Load**:
- Call `authStore.initialize()` in root layout
- Optionally hydrate from SSR data with `setUser()`
6. **Use Throughout App**:
- Access reactive state: `authStore.user`, `authStore.loading`
- Call methods: `signIn()`, `signUp()`, `signOut()`
- Check auth: `authStore.isAuthenticated`
### Best Practices
- Always use type generics for your user type
- Handle loading and error states in UI
- Clear errors after showing to user with `clearError()`
- Initialize store early in app lifecycle
- Use SSR hydration for better UX
- Keep auth service adapter thin - delegate to API layer
- Store tokens securely (httpOnly cookies preferred)
## Common Patterns
### Protected Route Pattern
```typescript
// In your +page.ts
import { authStore } from '$lib/stores/auth';
import { redirect } from '@sveltejs/kit';
export const load = async () => {
const isAuth = await authStore.checkAuth();
if (!isAuth) {
throw redirect(302, '/login');
}
};
```
### Automatic Token Refresh
```typescript
// Add to your auth service adapter
async isAuthenticated(): Promise<boolean> {
const token = getToken();
if (!token) return false;
if (isTokenExpired(token)) {
const refreshed = await refreshToken();
return refreshed;
}
return true;
}
```
### Error Boundary Pattern
```svelte
<script lang="ts">
import { authStore } from '$lib/stores/auth';
$effect(() => {
if (authStore.error) {
// Show toast notification
toast.error(authStore.error);
// Clear after showing
setTimeout(() => authStore.clearError(), 3000);
}
});
</script>
```
## Notes
- All stores use Svelte 5 runes exclusively
- Stores are framework-agnostic (work in any Svelte 5 context)
- No opinions on token storage or API implementation
- Fully type-safe with TypeScript generics
- Compatible with SSR and SPA patterns
- Optimized for reactivity and performance

View file

@ -0,0 +1,21 @@
# Memory: @manacore/shared-auth-stores
## Recent Changes
<!-- Track recent modifications, updates, and decisions made to this package -->
## Known Issues
<!-- Document any known bugs, limitations, or technical debt -->
## Future Enhancements
<!-- Ideas for future improvements or features -->
## Usage Notes
<!-- Record specific usage patterns discovered during development -->
## Integration Examples
<!-- Document real-world integration examples from apps using this package -->

View file

@ -0,0 +1,509 @@
# Agent: @manacore/shared-auth-ui
## Module Information
**Package:** `@manacore/shared-auth-ui`
**Type:** Shared Package (Svelte 5 Auth UI Components)
**Location:** `/packages/shared-auth-ui`
**Dependencies:** `@manacore/shared-auth`, `@manacore/shared-icons`, `svelte@^5.0.0`
## Identity
I am the authentication UI component library specialist for the ManaCore monorepo. I provide beautiful, accessible, and fully customizable authentication pages and components built with Svelte 5. I handle login, registration, password reset, and social authentication (Google, Apple) with built-in theming, animations, and accessibility features.
## Expertise
### Core Capabilities
1. **Complete Auth Pages**
- `LoginPage` - Full-featured login with email/password and social auth
- `RegisterPage` - Account creation with validation
- `ForgotPasswordPage` - Password reset flow
2. **Social Authentication Components**
- `GoogleSignInButton` - Google OAuth integration with official SDK
- `AppleSignInButton` - Apple Sign In with JS SDK
3. **Theming & Customization**
- Dynamic light/dark mode support
- Customizable colors, backgrounds, logos
- Fully customizable translations (i18n ready)
- Snippet-based extensibility
4. **Accessibility & UX**
- WCAG 2.1 AA compliant
- Keyboard navigation support
- Screen reader announcements
- Error handling with field-level validation
- Loading states and success animations
### Technical Patterns
- **Svelte 5 Runes Only**: All components use $state, $derived, $effect
- **Snippets for Extensibility**: Header controls, app sliders, custom content
- **Type-Safe Props**: Full TypeScript interfaces for all components
- **Callback-Based**: Components emit callbacks, app handles logic
- **CSS-in-Svelte**: Scoped styles with CSS variables
## Code Structure
```
src/
├── index.ts # Public exports
├── types.ts # Shared type definitions
├── pages/
│ ├── LoginPage.svelte # Full login page with validation
│ ├── RegisterPage.svelte # Registration page
│ └── ForgotPasswordPage.svelte # Password reset page
├── components/
│ ├── GoogleSignInButton.svelte # Google OAuth button
│ └── AppleSignInButton.svelte # Apple Sign In button
└── utils/
├── googleAuth.ts # Google OAuth SDK helpers
└── appleAuth.ts # Apple Sign In SDK helpers
```
## Key Files
### `src/types.ts`
Core type definitions:
- `AuthUIConfig` - Configuration for auth pages
- `AuthServiceInterface` - Interface for auth callbacks
- `AuthResult` - Standard auth operation result
### `src/pages/LoginPage.svelte`
Comprehensive login page (1000+ lines):
- Email/password authentication
- Optional Google/Apple sign in
- Field-level validation with error highlighting
- Remember me checkbox
- Forgot password link
- Theme toggle (light/dark)
- Success animations
- Accessibility features (skip links, ARIA)
- Customizable translations
- Responsive design
### `src/pages/RegisterPage.svelte`
Registration page with:
- Email/password/confirm password fields
- Real-time password strength indicator
- Password visibility toggle
- Terms of service checkbox
- Social authentication options
- Similar theming and accessibility as LoginPage
### `src/pages/ForgotPasswordPage.svelte`
Password reset flow:
- Email input with validation
- Success state handling
- Back to login navigation
- Consistent styling with other pages
### `src/components/GoogleSignInButton.svelte`
Google OAuth integration:
- Loads Google Identity Services SDK
- Renders official Google Sign In button
- Handles token validation
- Loading states during authentication
- Error handling
### `src/components/AppleSignInButton.svelte`
Apple Sign In integration:
- Loads Apple JS SDK
- Renders official Apple button
- Handles identity token flow
- Redirect URI management
### `src/utils/googleAuth.ts`
Google OAuth helpers:
- `setGoogleClientId()` - Configure client ID
- `initializeGoogleAuth()` - Initialize SDK
- `renderGoogleButton()` - Render button with options
- `waitForGoogleAuth()` - Wait for SDK load
### `src/utils/appleAuth.ts`
Apple Sign In helpers:
- `setAppleConfig()` - Configure Apple credentials
- `initializeAppleAuth()` - Initialize SDK
- `signInWithApple()` - Trigger sign in flow
- `parseAppleAuthorizationResponse()` - Handle callback
## Integration Points
### Consumed By
- **Web Apps** (SvelteKit): Import pages in auth routes
- **Landing Pages** (Astro): Can embed auth pages
- Any Svelte 5 application with authentication needs
### Dependencies
- `@manacore/shared-icons` - Icon components (Eye, Check, Warning, etc.)
- `@manacore/shared-auth` - Auth utilities (may be deprecated, check usage)
- `svelte@^5.0.0` - Peer dependency
### Related Packages
- `@manacore/shared-auth-stores` - State management for auth
- `@manacore/shared-nestjs-auth` - Backend auth validation
- `services/mana-core-auth` - Central auth service
## Key Patterns
### 1. Basic Login Page Setup
```svelte
<script lang="ts">
import { LoginPage } from '@manacore/shared-auth-ui';
import { goto } from '$app/navigation';
import Logo from '$lib/components/Logo.svelte';
import type { AuthResult } from '@manacore/shared-auth-ui';
async function handleSignIn(email: string, password: string): Promise<AuthResult> {
const response = await fetch('/api/auth/sign-in', {
method: 'POST',
body: JSON.stringify({ email, password })
});
if (!response.ok) {
return { success: false, error: 'Invalid credentials' };
}
return { success: true };
}
</script>
<LoginPage
appName="MyApp"
logo={Logo}
primaryColor="#6366f1"
onSignIn={handleSignIn}
goto={goto}
/>
```
### 2. Login with Google OAuth
```svelte
<script lang="ts">
import { LoginPage } from '@manacore/shared-auth-ui';
import { goto } from '$app/navigation';
import Logo from '$lib/components/Logo.svelte';
import { PUBLIC_GOOGLE_CLIENT_ID } from '$env/static/public';
async function handleGoogleSignIn(idToken: string): Promise<AuthResult> {
const response = await fetch('/api/auth/google', {
method: 'POST',
body: JSON.stringify({ idToken })
});
if (!response.ok) {
return { success: false, error: 'Google sign in failed' };
}
return { success: true };
}
</script>
<svelte:head>
<script src="https://accounts.google.com/gsi/client" async></script>
<meta name="google-signin-client_id" content={PUBLIC_GOOGLE_CLIENT_ID} />
</svelte:head>
<LoginPage
appName="MyApp"
logo={Logo}
primaryColor="#6366f1"
onSignIn={handleSignIn}
onSignInWithGoogle={handleGoogleSignIn}
goto={goto}
enableGoogle={true}
/>
```
### 3. Fully Customized Login Page
```svelte
<script lang="ts">
import { LoginPage } from '@manacore/shared-auth-ui';
import { goto } from '$app/navigation';
import Logo from '$lib/components/Logo.svelte';
import AppCarousel from '$lib/components/AppCarousel.svelte';
import LanguageSelector from '$lib/components/LanguageSelector.svelte';
const translations = {
title: 'Willkommen zurück',
subtitle: 'Melden Sie sich bei Ihrem Konto an',
emailPlaceholder: 'E-Mail',
passwordPlaceholder: 'Passwort',
signInButton: 'Anmelden',
// ... other translations
};
</script>
<LoginPage
appName="MyApp"
logo={Logo}
primaryColor="#6366f1"
darkPrimaryColor="#818cf8"
lightBackground="#f9fafb"
darkBackground="#0f172a"
onSignIn={handleSignIn}
goto={goto}
successRedirect="/dashboard"
registerPath="/register"
forgotPasswordPath="/forgot-password"
translations={translations}
>
{#snippet headerControls()}
<LanguageSelector />
{/snippet}
{#snippet appSlider()}
<AppCarousel />
{/snippet}
</LoginPage>
```
### 4. Register Page with Custom Validation
```svelte
<script lang="ts">
import { RegisterPage } from '@manacore/shared-auth-ui';
import { goto } from '$app/navigation';
import Logo from '$lib/components/Logo.svelte';
async function handleSignUp(email: string, password: string): Promise<AuthResult> {
// Custom validation
if (password.length < 12) {
return {
success: false,
error: 'Password must be at least 12 characters'
};
}
const response = await fetch('/api/auth/sign-up', {
method: 'POST',
body: JSON.stringify({ email, password })
});
if (!response.ok) {
const data = await response.json();
return { success: false, error: data.message };
}
const data = await response.json();
return {
success: true,
needsVerification: data.needsEmailVerification
};
}
</script>
<RegisterPage
appName="MyApp"
logo={Logo}
primaryColor="#6366f1"
onSignUp={handleSignUp}
goto={goto}
/>
```
### 5. Forgot Password Flow
```svelte
<script lang="ts">
import { ForgotPasswordPage } from '@manacore/shared-auth-ui';
import { goto } from '$app/navigation';
import Logo from '$lib/components/Logo.svelte';
async function handleForgotPassword(email: string): Promise<AuthResult> {
const response = await fetch('/api/auth/forgot-password', {
method: 'POST',
body: JSON.stringify({ email })
});
if (!response.ok) {
return { success: false, error: 'Failed to send reset email' };
}
return { success: true };
}
</script>
<ForgotPasswordPage
appName="MyApp"
logo={Logo}
primaryColor="#6366f1"
onForgotPassword={handleForgotPassword}
goto={goto}
/>
```
### 6. Standalone Google Sign In Button
```svelte
<script lang="ts">
import { GoogleSignInButton } from '@manacore/shared-auth-ui';
async function handleGoogleSuccess(idToken: string) {
// Validate token with your backend
const response = await fetch('/api/auth/google', {
method: 'POST',
body: JSON.stringify({ idToken })
});
if (response.ok) {
goto('/dashboard');
}
}
function handleGoogleError(error: Error) {
console.error('Google sign in failed:', error);
}
</script>
<GoogleSignInButton
onSuccess={handleGoogleSuccess}
onError={handleGoogleError}
/>
```
## How to Use
### For App Developers
1. **Install Dependencies**:
```bash
pnpm add @manacore/shared-auth-ui @manacore/shared-icons
```
2. **Create Auth Routes**:
```
src/routes/
├── login/+page.svelte
├── register/+page.svelte
└── forgot-password/+page.svelte
```
3. **Import and Configure Pages**:
- Import the desired page component
- Provide required props (appName, logo, primaryColor)
- Implement auth callback functions
- Configure optional features (social auth, translations)
4. **Implement Auth Callbacks**:
- Return `AuthResult` objects from all callbacks
- Handle success/error states appropriately
- Set `needsVerification: true` if email verification required
5. **Add Social Auth (Optional)**:
- Google: Add SDK script tag, set client ID, enable in props
- Apple: Configure Apple credentials, enable in props
- Implement server-side token validation
6. **Customize Styling**:
- Set custom colors via props
- Provide custom backgrounds for light/dark modes
- Override translations for internationalization
- Use snippets for additional UI elements
### Integration with Auth Stores
```svelte
<script lang="ts">
import { LoginPage } from '@manacore/shared-auth-ui';
import { authStore } from '$lib/stores/auth';
import { goto } from '$app/navigation';
import Logo from '$lib/components/Logo.svelte';
async function handleSignIn(email: string, password: string) {
// Auth store handles the API call and state management
const result = await authStore.signIn(email, password);
return result;
}
</script>
<LoginPage
appName="MyApp"
logo={Logo}
primaryColor="#6366f1"
onSignIn={handleSignIn}
goto={goto}
/>
```
### Environment Variables
For social authentication, set these in your `.env` files:
```bash
# Google OAuth
PUBLIC_GOOGLE_CLIENT_ID=your-google-client-id
# Apple Sign In
PUBLIC_APPLE_CLIENT_ID=your-apple-service-id
PUBLIC_APPLE_REDIRECT_URI=https://yourdomain.com/auth/apple/callback
```
### Best Practices
- Always validate tokens on the server side
- Use httpOnly cookies for session tokens
- Implement rate limiting on auth endpoints
- Provide clear, user-friendly error messages
- Test keyboard navigation and screen readers
- Customize translations for your target audience
- Handle edge cases (network errors, timeouts)
- Implement proper CORS for social auth callbacks
## Translation System
All pages accept a `translations` prop with the following pattern:
```typescript
interface LoginTranslations {
title: string;
subtitle: string;
emailPlaceholder: string;
passwordPlaceholder: string;
signInButton: string;
// ... and more
}
```
Default English translations are provided, but you can override any or all strings for internationalization.
## Accessibility Features
- Skip links for keyboard users
- ARIA labels and roles
- Screen reader announcements for state changes
- Focus management after errors
- High contrast mode support
- Reduced motion support
- Semantic HTML structure
- Keyboard-only navigation
## Theme System
Pages automatically detect system color scheme preference and provide a manual toggle. Theme state is managed internally and applies to:
- Background colors
- Text colors
- Input styles
- Button styles
- Border colors
- Shadow intensities
You can customize colors via props:
- `primaryColor` - Brand color (buttons, links, accents)
- `lightBackground` - Light mode background
- `darkBackground` - Dark mode background
## Notes
- All components built with Svelte 5 runes exclusively
- Fully type-safe with comprehensive TypeScript interfaces
- Production-ready with error handling and loading states
- Optimized for performance (lazy SDK loading, efficient animations)
- Mobile-responsive with touch-friendly targets
- Works in SSR and SPA contexts
- No external CSS dependencies (Tailwind not required)

View file

@ -0,0 +1,21 @@
# Memory: @manacore/shared-auth-ui
## Recent Changes
<!-- Track recent modifications, updates, and decisions made to this package -->
## Known Issues
<!-- Document any known bugs, limitations, or technical debt -->
## Future Enhancements
<!-- Ideas for future improvements or features -->
## Usage Notes
<!-- Record specific usage patterns discovered during development -->
## Integration Examples
<!-- Document real-world integration examples from apps using this package -->

View file

@ -0,0 +1,100 @@
# Shared Auth Expert
## Module: @manacore/shared-auth
**Path:** `packages/shared-auth`
**Description:** Cross-platform authentication library providing JWT token management, automatic token refresh, and multi-device session handling for all ManaCore apps (web and mobile).
**Tech Stack:** TypeScript, JWT (EdDSA), Platform adapters (Web/React Native)
**Key Dependencies:** `@manacore/shared-types`, `@manacore/better-auth-types`, `base64-js`
## Identity
You are the **Shared Auth Expert**. You have deep knowledge of:
- JWT token lifecycle management (access tokens, refresh tokens, expiration handling)
- Multi-platform authentication (Web localStorage, React Native AsyncStorage)
- Token refresh strategies with retry logic and request queuing
- B2B authentication patterns and organizational roles
- Device binding and multi-device session management
## Expertise
- JWT decoding and validation without external dependencies
- Automatic token refresh with request queuing during refresh cycles
- Platform-agnostic storage, device, and network adapters
- Social authentication flows (Google, Apple sign-in)
- Credit system integration for subscription-based apps
- Offline handling with graceful degradation
## Code Structure
```
packages/shared-auth/src/
├── core/
│ ├── authService.ts # Main auth operations (sign in, sign up, sign out, refresh)
│ ├── tokenManager.ts # Token lifecycle, state machine, request queuing
│ └── jwtUtils.ts # JWT decode, validation, B2B checks
├── adapters/
│ ├── storage.ts # Platform storage (localStorage, AsyncStorage, memory)
│ ├── device.ts # Device info and ID management
│ └── network.ts # Online/offline detection
├── interceptors/
│ └── fetchInterceptor.ts # Automatic Authorization header injection
├── clients/
│ └── contactsClient.ts # Cross-app contact integration
└── types/
└── index.ts # All TypeScript interfaces
```
## Key Patterns
### Token State Machine
The `TokenManager` implements a state machine with states: `IDLE`, `REFRESHING`, `VALID`, `EXPIRED`, `EXPIRED_OFFLINE`. Subscribe to state changes for UI updates.
### Request Queuing
During token refresh, concurrent 401 responses queue requests. Once refresh completes, all queued requests retry automatically with the new token.
### Platform Adapters
Initialize with platform-specific adapters before using:
```typescript
// Web
import { initializeWebAuth } from '@manacore/shared-auth';
const { authService, tokenManager } = initializeWebAuth({ baseUrl: '...' });
// Mobile - set adapters manually
setStorageAdapter(createAsyncStorageAdapter());
setDeviceAdapter(createExpoDeviceAdapter());
```
### B2B Claims
JWT tokens contain B2B claims: `is_b2b`, `app_settings.b2b.organizationId`, `disableRevenueCat`. Use helpers like `isB2BUser()`, `getB2BInfo()`.
## Integration Points
- **Used by:** All frontend apps (chat, picture, zitare, contacts, etc.), mobile apps via Expo
- **Depends on:** `@manacore/shared-types`, `@manacore/better-auth-types`
- **Backend:** Integrates with `services/mana-core-auth` (port 3001)
## Common Tasks
### Check authentication status
```typescript
const isAuth = await authService.isAuthenticated();
const user = await authService.getUserFromToken();
```
### Handle token refresh manually
```typescript
const result = await tokenManager.refreshToken();
if (!result.success) {
// Handle expired session
}
```
### Subscribe to auth state changes
```typescript
const unsubscribe = tokenManager.subscribe((state, token) => {
if (state === TokenState.EXPIRED) {
// Redirect to login
}
});
```
## How to Use
```
"Read packages/shared-auth/.agent/ and help me with..."
```

View file

@ -0,0 +1,15 @@
# Shared Auth Expert - Memory
Auto-updated with learnings from code changes.
## Recent Updates
*No updates yet.*
## Known Issues
*None documented.*
## Implementation Notes
- EdDSA JWT tokens used (not RSA)
- Mana Core Auth uses `accessToken` field (not `token`)
- Default storage keys: `@auth/appToken`, `@auth/refreshToken`, `@auth/userEmail`
- Refresh cooldown: 5000ms, Max refresh attempts: 3

View file

@ -0,0 +1,483 @@
# Agent: @manacore/shared-branding
## Module Information
**Package**: `@manacore/shared-branding`
**Type**: Shared Svelte Component Library
**Purpose**: Centralized branding assets, logos, app metadata, and visual identity for the entire ManaCore ecosystem
## Identity
I am the Branding Agent, the single source of truth for all visual branding assets across the ManaCore ecosystem. I provide logos, app metadata, color schemes, SVG icons, and branding configuration for all apps in the platform.
## Expertise
### Core Capabilities
1. **App Logos**: SVG logo components for all ManaCore apps
2. **Branding Configuration**: Colors, names, taglines for each app
3. **App Icons**: Data URL SVG icons for favicons and app tiles
4. **Mana Icon**: Core brand identity droplet icon
5. **App Registry**: Complete catalog of ManaCore apps with status
6. **Logo Components**: Generic and pre-configured logo components
7. **Navigation Integration**: App data for pill navigation and app switchers
### Technical Stack
- Svelte 5 components
- SVG graphics (inline and data URLs)
- TypeScript for type safety
- Centralized configuration objects
## Code Structure
```
packages/shared-branding/src/
├── index.ts # Main exports
├── types.ts # TypeScript type definitions
├── config.ts # App branding configuration
├── app-icons.ts # SVG data URL icons
├── mana-apps.ts # Complete app registry
├── AppLogo.svelte # Generic configurable logo
├── AppLogoWithName.svelte # Logo with app name
├── ManaIcon.svelte # Mana droplet icon
└── logos/
├── index.ts # Logo component exports
├── MemoroLogo.svelte
├── ManaCoreLogo.svelte
├── ManaDeckLogo.svelte
├── ChatLogo.svelte
├── ZitareLogo.svelte
├── ContactsLogo.svelte
├── CalendarLogo.svelte
├── StorageLogo.svelte
└── ... (all app logos)
```
## Key Patterns
### App Branding Configuration
```typescript
export interface AppBranding {
id: AppId;
name: string; // Display name
tagline: string; // Short description
primaryColor: string; // Hex color
secondaryColor: string; // Hex color
logoPath: string; // SVG path data
logoViewBox: string; // SVG viewBox
logoStroke: boolean; // Is stroke-based?
logoStrokeWidth?: number; // Stroke width if applicable
}
```
### App Registry
```typescript
export interface ManaApp {
id: AppId;
name: string;
tagline: string;
icon: string; // Icon identifier
color: string; // Primary color
status: AppStatus; // 'active' | 'development' | 'concept'
category: string; // App category
description?: string; // Detailed description
url?: string; // Production URL
}
```
### Logo Component Pattern
All logo components accept:
```typescript
export interface LogoProps {
size?: number; // Width/height in pixels
class?: string; // Additional CSS classes
color?: string; // Override color
showName?: boolean; // Show app name (for AppLogoWithName)
}
```
## Integration Points
### Consumed By
- All web apps - Logo displays and branding
- Navigation components - App switchers and pill navigation
- Landing pages - App showcases
- Documentation - App references
### Dependencies
- `svelte@^5.0.0` - Component framework
## How to Use
### 1. Import Pre-configured Logos
```svelte
<script lang="ts">
import {
MemoroLogo,
ChatLogo,
CalendarLogo,
ContactsLogo,
} from '@manacore/shared-branding';
</script>
<MemoroLogo size={48} />
<ChatLogo size={32} class="mr-2" />
<CalendarLogo size={64} color="#ff0000" />
```
### 2. Use Generic Logo Component
```svelte
<script lang="ts">
import { AppLogo, getAppBranding } from '@manacore/shared-branding';
const branding = getAppBranding('memoro');
</script>
<AppLogo
logoPath={branding.logoPath}
viewBox={branding.logoViewBox}
color={branding.primaryColor}
size={48}
stroke={branding.logoStroke}
strokeWidth={branding.logoStrokeWidth}
/>
```
### 3. Logo with App Name
```svelte
<script lang="ts">
import { AppLogoWithName } from '@manacore/shared-branding';
</script>
<AppLogoWithName
appId="memoro"
size={40}
showName={true}
namePosition="right"
/>
```
### 4. Use Mana Icon
```svelte
<script lang="ts">
import { ManaIcon } from '@manacore/shared-branding';
</script>
<ManaIcon size={32} class="text-primary" />
```
### 5. Get App Branding Data
```typescript
import { getAppBranding, getAllAppBrandings } from '@manacore/shared-branding';
// Single app branding
const memoroBranding = getAppBranding('memoro');
console.log(memoroBranding.name); // "Memoro"
console.log(memoroBranding.tagline); // "AI Voice Memos"
console.log(memoroBranding.primaryColor); // "#f8d62b"
// All apps
const allBrandings = getAllAppBrandings();
```
### 6. Use App Icons (Data URLs)
```typescript
import { APP_ICONS, type AppIconId } from '@manacore/shared-branding';
// As image source
const iconUrl = APP_ICONS.memoro;
// data:image/svg+xml,...
// In HTML
<img src={APP_ICONS.memoro} alt="Memoro" />
// As favicon
<link rel="icon" type="image/svg+xml" href={APP_ICONS.manacore} />
```
### 7. Access App Registry
```typescript
import {
MANA_APPS,
getManaApp,
getActiveManaApps,
getManaAppsByStatus,
} from '@manacore/shared-branding';
// Get specific app
const memoroApp = getManaApp('memoro');
// Get active apps
const activeApps = getActiveManaApps();
// Get apps by status
const devApps = getManaAppsByStatus('development');
// All apps
MANA_APPS.forEach(app => {
console.log(`${app.name}: ${app.status}`);
});
```
### 8. Get App URLs
```typescript
import { APP_URLS } from '@manacore/shared-branding';
const memoroUrl = APP_URLS.memoro; // Production URL
const chatUrl = APP_URLS.chat; // Production URL
```
### 9. Pill Navigation Integration
```typescript
import { getPillAppItems } from '@manacore/shared-branding';
import type { PillAppItemConfig } from '@manacore/shared-branding';
const pillItems = getPillAppItems(['memoro', 'chat', 'calendar']);
// Returns array of { id, name, icon, color, url } for PillNav
```
## Available Apps
### Active Apps
- **memoro** - AI Voice Memos (gold)
- **chat** - AI Chat Assistant (cyan)
- **picture** - AI Image Generation (blue)
- **zitare** - Daily Inspiration (amber)
- **contacts** - Contact Management (blue)
- **calendar** - Smart Calendar (cyan)
- **manacore** - Central Hub (indigo)
### Development Apps
- **manadeck** - AI Flashcards (purple)
- **maerchenzauber** - AI Story Creator (pink)
- **presi** - Presentation Creator (orange)
- **nutriphi** - AI Nutrition Tracker (green)
- **storage** - Cloud Storage (blue)
- **clock** - Clocks & Alarms (amber)
- **todo** - Task Management (purple)
- **mail** - Smart Email Client (indigo)
- **moodlit** - Ambient Lighting (purple)
- **inventory** - Inventory Management (teal)
- **uload** - Smart URL Shortener (blue)
## Logo Specifications
### Memoro Logo
- Type: Custom smile/face design
- Format: Filled SVG path
- Primary Color: #f8d62b (gold)
- Secondary Color: #f7d44c
- Distinctive Feature: Unique smile face shape
### ManaCore Logo
- Type: Geometric hexagon/core
- Format: Stroke-based SVG
- Primary Color: #6366f1 (indigo)
- Secondary Color: #818cf8
- Distinctive Feature: Layered hexagon structure
### Mana Icon
- Type: Water droplet
- Format: Gradient-filled SVG
- Primary Color: #4287f5 (blue)
- Usage: Main brand identity mark
### Generic App Logos
Most apps use stroke-based icons from Heroicons with:
- Stroke width: 1.5-2px
- ViewBox: 0 0 24 24
- Customizable colors
## Common Patterns
### App Card with Logo
```svelte
<script lang="ts">
import { MemoroLogo, getAppBranding } from '@manacore/shared-branding';
const branding = getAppBranding('memoro');
</script>
<div class="app-card">
<MemoroLogo size={48} />
<h3>{branding.name}</h3>
<p>{branding.tagline}</p>
</div>
<style>
.app-card {
background-color: {branding.primaryColor}20;
border-left: 4px solid {branding.primaryColor};
}
</style>
```
### App Switcher
```svelte
<script lang="ts">
import { getActiveManaApps } from '@manacore/shared-branding';
import * as Logos from '@manacore/shared-branding';
const apps = getActiveManaApps();
</script>
<div class="app-switcher">
{#each apps as app}
<a href={app.url} class="app-item">
<svelte:component this={Logos[`${app.id}Logo`]} size={32} />
<span>{app.name}</span>
</a>
{/each}
</div>
```
### Favicon Setup
```svelte
<script lang="ts">
import { APP_ICONS } from '@manacore/shared-branding';
</script>
<svelte:head>
<link rel="icon" type="image/svg+xml" href={APP_ICONS.memoro} />
<link rel="apple-touch-icon" href={APP_ICONS.memoro} />
</svelte:head>
```
### Branded Button
```svelte
<script lang="ts">
import { MemoroLogo, getAppBranding } from '@manacore/shared-branding';
const branding = getAppBranding('memoro');
</script>
<button style="background-color: {branding.primaryColor}">
<MemoroLogo size={20} color="white" />
Open {branding.name}
</button>
```
## Best Practices
1. **Use pre-configured logos**: Prefer `MemoroLogo` over generic `AppLogo`
2. **Consistent sizing**: Standard sizes: 16, 20, 24, 32, 48, 64
3. **Color overrides**: Only override colors when necessary
4. **Accessibility**: Always provide alt text for logos
5. **Performance**: SVG logos are lightweight and scalable
6. **Brand consistency**: Use branding config for colors, not hardcoded values
7. **Icon data URLs**: Use for favicons and app tiles
8. **App registry**: Reference MANA_APPS for app metadata
## Common Tasks
### Add New App Logo
1. Create logo component in `src/logos/NewAppLogo.svelte`
2. Add branding config to `APP_BRANDING` in `config.ts`
3. Generate SVG icon data URL in `app-icons.ts`
4. Add to `MANA_APPS` registry in `mana-apps.ts`
5. Export from `src/logos/index.ts`
6. Export from main `src/index.ts`
### Update App Colors
```typescript
// src/config.ts
export const APP_BRANDING = {
myapp: {
id: 'myapp',
name: 'MyApp',
tagline: 'My App Tagline',
primaryColor: '#ff6b6b', // Update here
secondaryColor: '#ff8787', // Update here
// ...
},
};
```
### Create Custom Logo Variant
```svelte
<script lang="ts">
import { AppLogo, getAppBranding } from '@manacore/shared-branding';
const branding = getAppBranding('memoro');
</script>
<!-- Monochrome variant -->
<AppLogo
logoPath={branding.logoPath}
viewBox={branding.logoViewBox}
color="currentColor"
size={32}
class="text-foreground"
/>
```
### Generate App Icon for Meta Tags
```svelte
<script lang="ts">
import { APP_ICONS } from '@manacore/shared-branding';
</script>
<svelte:head>
<meta property="og:image" content={APP_ICONS.memoro} />
<meta name="twitter:image" content={APP_ICONS.memoro} />
</svelte:head>
```
## App Categories
- **Productivity**: memoro, calendar, contacts, todo, mail
- **AI Tools**: chat, picture, manadeck, nutriphi, maerchenzauber
- **Content**: zitare, presi, storyteller
- **Utilities**: storage, clock, uload, inventory
- **Entertainment**: moodlit
- **Platform**: manacore (central hub)
## Color Palette
### App Primary Colors
- Memoro: #f8d62b (Gold)
- ManaCore: #6366f1 (Indigo)
- ManaDeck: #8b5cf6 (Purple)
- Chat: #0ea5e9 (Cyan)
- Picture: #3b82f6 (Blue)
- Zitare: #f59e0b (Amber)
- Calendar: #0ea5e9 (Cyan)
- Contacts: #3b82f6 (Blue)
- Nutriphi: #10b981 (Green)
- Presi: #f97316 (Orange)
- Märchenzauber: #ec4899 (Pink)
### Brand Color
- Mana: #4287f5 (Blue) - Used for subscriptions/pricing
## Notes
- All logos are SVG-based for scalability
- Components use Svelte 5 syntax
- Logo sizes are in pixels (width/height are equal)
- Color props accept hex, rgb, or CSS color names
- Data URL icons are optimized for file size
- App registry includes status tracking (active/development/concept)
- Branding config is the single source of truth
- Pre-configured logo components are recommended for consistency

View file

@ -0,0 +1,17 @@
# Memory: @manacore/shared-branding
## Recent Changes
<!-- Document recent changes, decisions, and context here -->
## Active Patterns
<!-- Track recurring patterns or architectural decisions -->
## Known Issues
<!-- Track known bugs or limitations -->
## Future Enhancements
<!-- Ideas for future improvements -->

View file

@ -0,0 +1,307 @@
# Shared Config Agent
## Module Information
**Package**: `@manacore/shared-config`
**Version**: 1.0.0
**Type**: ESM TypeScript configuration library
**Dependencies**: `zod` 3.24.0
## Identity
I am the Shared Config Agent, responsible for managing configuration utilities across all ManaCore applications. I provide type-safe environment variable validation, API endpoint construction, feature flag management, and application metadata handling using Zod schemas.
## Expertise
- **Environment Validation**: Zod-based env var schemas with type safety
- **API Utilities**: URL building, query parameters, path joining
- **Feature Flags**: Runtime feature toggles with environment overrides
- **App Metadata**: Version, build, and deployment information
- **HTTP Constants**: Status codes and method constants
- **Type Safety**: Full TypeScript inference from Zod schemas
## Code Structure
```
src/
├── index.ts # Main export barrel
├── env.ts # Environment variable validation
├── api.ts # API endpoint construction
└── features.ts # Feature flags and app metadata
```
### Export Structure
The package provides targeted exports for tree-shaking:
- `.` - All utilities
- `./env` - Environment utilities only
- `./api` - API utilities only
- `./features` - Feature flags only
## Key Patterns
### 1. Environment Validation Pattern
Uses Zod for runtime validation with TypeScript inference:
```typescript
import { z } from 'zod';
import { validateEnv, envSchemas } from '@manacore/shared-config';
// Define schema
const schema = z.object({
NODE_ENV: envSchemas.nodeEnv,
PORT: envSchemas.port.default(3000),
DATABASE_URL: envSchemas.url,
ENABLE_FEATURE_X: envSchemas.boolean.default(false),
});
// Validate and get typed config
const config = validateEnv(schema);
// Type: { NODE_ENV: 'development' | 'production' | 'test', PORT: number, ... }
```
### 2. Common Environment Schemas
Pre-built schemas for common use cases:
```typescript
// Available schemas
envSchemas.url // URL validation
envSchemas.nonEmpty // Non-empty string
envSchemas.optional // Optional string
envSchemas.port // Port number (1-65535)
envSchemas.boolean // Boolean (true/false/1/0/yes/no/on/off)
envSchemas.number // Number coercion
envSchemas.positiveInt // Positive integer
envSchemas.email // Email validation
envSchemas.nodeEnv // 'development' | 'production' | 'test'
// Pre-built schemas
supabaseEnvSchema // SUPABASE_URL, SUPABASE_ANON_KEY, etc.
appEnvSchema // NODE_ENV, PORT
```
### 3. API Builder Pattern
Fluent API for constructing endpoints:
```typescript
import { createApiBuilder } from '@manacore/shared-config';
const api = createApiBuilder({
baseUrl: 'https://api.example.com',
version: 'v1',
timeout: 5000,
headers: { 'X-App': 'manacore' }
});
// Build endpoints
api.endpoint('users', userId);
// "https://api.example.com/v1/users/123"
api.endpointWithQuery('users', { role: 'admin', active: true });
// "https://api.example.com/v1/users?role=admin&active=true"
api.endpointWithQuery(['users', userId, 'posts'], { limit: 10 });
// "https://api.example.com/v1/users/123/posts?limit=10"
```
### 4. Feature Flag Pattern
Runtime feature toggles with environment overrides:
```typescript
import { createFeatureFlags } from '@manacore/shared-config';
const flags = createFeatureFlags({
darkMode: {
key: 'darkMode',
defaultEnabled: true,
description: 'Dark mode support',
envVar: 'FEATURE_DARK_MODE',
},
aiChat: {
key: 'aiChat',
defaultEnabled: false,
description: 'AI chat feature',
},
});
// Check if enabled
if (flags.isEnabled('darkMode')) {
// Enable dark mode
}
// Get all enabled features
const enabled = flags.getEnabledFeatures();
// ['darkMode']
// Simple feature check
import { isFeatureEnabled } from '@manacore/shared-config';
if (isFeatureEnabled('experimental_ui', false)) {
// FEATURE_EXPERIMENTAL_UI=true in env
}
```
### 5. App Metadata Pattern
Centralized version and build information:
```typescript
import { createAppMetadata, formatVersion } from '@manacore/shared-config';
const metadata = createAppMetadata({
name: 'ManaCore Chat',
version: '2.1.0',
description: 'AI-powered chat application',
});
// Auto-populated from env
metadata.buildNumber // BUILD_NUMBER or VITE_BUILD_NUMBER
metadata.commitHash // COMMIT_HASH or VITE_COMMIT_HASH or GIT_COMMIT
metadata.buildTime // BUILD_TIME or VITE_BUILD_TIME
metadata.environment // NODE_ENV
// Format for display
formatVersion(metadata);
// "2.1.0 (42) [a1b2c3d]"
```
## Integration Points
### With Backend Services (NestJS)
```typescript
// Backend environment validation
const config = validateEnv(z.object({
NODE_ENV: envSchemas.nodeEnv,
PORT: envSchemas.port.default(3000),
DATABASE_URL: envSchemas.url,
MANA_CORE_AUTH_URL: envSchemas.url,
S3_ENDPOINT: envSchemas.url,
}));
// Build internal API URLs
const authApi = createApiBuilder({
baseUrl: config.MANA_CORE_AUTH_URL,
version: 'v1'
});
```
### With Frontend Applications (SvelteKit)
```typescript
// Frontend environment validation
const config = validateEnv(z.object({
PUBLIC_API_URL: envSchemas.url,
PUBLIC_SUPABASE_URL: envSchemas.url,
PUBLIC_SUPABASE_ANON_KEY: envSchemas.nonEmpty,
}));
// Build API client
const api = createApiBuilder({
baseUrl: config.PUBLIC_API_URL,
version: 'v1',
});
```
### With Other Packages
- **@manacore/shared-auth**: Environment validation for auth config
- **@manacore/shared-storage**: Environment validation for S3 config
- **All Apps**: Feature flags, API builders, metadata display
## HTTP Utilities
### Status Codes
```typescript
import { HTTP_STATUS, isSuccessStatus } from '@manacore/shared-config';
if (response.status === HTTP_STATUS.NOT_FOUND) {
// Handle 404
}
if (isSuccessStatus(response.status)) {
// 2xx response
}
if (isClientError(response.status)) {
// 4xx response
}
if (isServerError(response.status)) {
// 5xx response
}
```
### Methods
```typescript
import { HTTP_METHODS } from '@manacore/shared-config';
fetch(url, {
method: HTTP_METHODS.POST,
// ...
});
```
## How to Use
### Installation
This package is internal to the monorepo. Add to dependencies in `package.json`:
```json
{
"dependencies": {
"@manacore/shared-config": "workspace:*"
}
}
```
### Import Examples
```typescript
// Environment validation
import { validateEnv, envSchemas, getRequiredEnv } from '@manacore/shared-config';
// API utilities
import { createApiBuilder, buildUrl } from '@manacore/shared-config';
// Feature flags
import { createFeatureFlags, isFeatureEnabled } from '@manacore/shared-config';
// HTTP constants
import { HTTP_STATUS, HTTP_METHODS } from '@manacore/shared-config';
// Targeted imports (tree-shakeable)
import { validateEnv } from '@manacore/shared-config/env';
import { createApiBuilder } from '@manacore/shared-config/api';
import { isFeatureEnabled } from '@manacore/shared-config/features';
```
### Best Practices
1. **Early Validation**: Validate environment at app startup, fail fast
2. **Type Inference**: Let Zod infer types, avoid manual type annotations
3. **Schema Reuse**: Use pre-built schemas (supabaseEnvSchema, appEnvSchema)
4. **Feature Flags**: Use for gradual rollouts and A/B testing
5. **API Builders**: Create once per service, reuse throughout app
6. **Error Messages**: Zod provides clear error messages for invalid env vars
### Common Use Cases
- **Backend Config**: Validate DATABASE_URL, API keys, service URLs
- **Frontend Config**: Validate PUBLIC_* variables for SvelteKit/Vite
- **API Clients**: Build type-safe API endpoint URLs
- **Feature Toggles**: Enable/disable features via environment variables
- **Version Display**: Show app version with build info in UI
## Error Handling
Environment validation throws descriptive errors:
```typescript
// Missing required variable
// Error: Environment validation failed:
// DATABASE_URL: Required
// Invalid URL
// Error: Environment validation failed:
// API_URL: Invalid url
// Invalid port
// Error: Environment validation failed:
// PORT: Number must be less than or equal to 65535
```
## Notes
- All utilities are tree-shakeable via targeted exports
- Zod schemas provide both runtime validation and TypeScript types
- Boolean env vars accept multiple formats (true/1/yes/on)
- Feature flags check both specific and generic env vars
- API builders normalize URLs (remove trailing slashes, handle leading slashes)

View file

@ -0,0 +1,16 @@
# Shared Config Memory
## Recent Changes
<!-- Track recent modifications, bug fixes, and enhancements -->
## Known Issues
<!-- Document any known bugs or limitations -->
## Schema Patterns
<!-- Document common schema patterns used across applications -->
## Feature Flag Usage
<!-- Track which features are using feature flags across apps -->
## Integration Notes
<!-- Notes about how this package integrates with other packages -->

View file

@ -0,0 +1,642 @@
# Shared Credit Service Agent
## Module Information
**Package**: `@manacore/shared-credit-service`
**Version**: 0.0.1
**Type**: TypeScript Service Library
**Purpose**: Framework-agnostic credit/mana management service providing balance fetching, operation pricing, credit checks, and consumption notifications for all ManaCore apps (SvelteKit, Expo, NestJS).
## Identity
I am the Credit Service Specialist, providing a unified API for managing mana/credit balances across the ManaCore ecosystem. I handle pricing lookups, balance checks, consumption tracking, and real-time credit updates with caching and fallback mechanisms.
## Expertise
- **Credit Balance Management**: Fetch and cache user credit balances
- **Operation Pricing**: Dynamic pricing with backend sync and fallback
- **Credit Checks**: Pre-operation balance validation
- **Consumption Notifications**: Event-driven credit update system
- **Caching Strategy**: 30-minute pricing cache with configurable duration
- **Fallback Mechanisms**: Graceful degradation when backend unavailable
- **Framework Agnostic**: Works with SvelteKit, Expo, and Node.js
- **Type-Safe API**: Full TypeScript support with Result types
- **Auth Integration**: Token-based authentication with configurable getter
## Code Structure
```
src/
├── index.ts # Barrel exports with usage examples
├── types.ts # TypeScript interfaces and constants
└── createCreditService.ts # Factory function implementation
```
## Core API
### Factory Function: `createCreditService(config)`
Creates a credit service instance with the following methods:
```typescript
const creditService = createCreditService({
apiUrl: 'https://api.example.com',
balanceEndpoint: '/auth/credits', // Optional, default shown
pricingEndpoint: '/credits/pricing', // Optional, default shown
cacheDuration: 30 * 60 * 1000, // Optional, 30 min default
fallbackPricing: { /* custom costs */ }, // Optional
getAuthToken: async () => token // Required
});
```
### Service Methods
#### Initialization
- `initialize()` - Preload pricing on app startup
#### Balance Operations
- `getBalance()` - Fetch current user credit balance
- `checkBalance(requiredCredits, operation?)` - Check if user has enough credits
- `checkOperationBalance(operation)` - Check balance for specific operation
#### Pricing Operations
- `getPricing()` - Fetch all operation costs (cached)
- `getOperationCost(operation)` - Get cost for operation (async, with cache)
- `getOperationCostSync(operation)` - Get cost synchronously (cache-only)
- `calculateCost(operation, quantity)` - Calculate cost for multiple units
- `calculateCostSync(operation, quantity)` - Calculate cost synchronously
#### Notifications
- `onCreditUpdate(callback)` - Subscribe to credit consumption events
- `triggerCreditUpdate(creditsConsumed, operation?)` - Manually trigger update
#### Utilities
- `formatCredits(amount, locale?)` - Format credit amount for display
- `clearCache()` - Clear pricing cache (for testing/refresh)
## Key Types
### Configuration
```typescript
interface CreditServiceConfig {
apiUrl: string; // Backend API base URL
balanceEndpoint?: string; // Default: '/auth/credits'
pricingEndpoint?: string; // Default: '/credits/pricing'
cacheDuration?: number; // Default: 30 minutes (ms)
fallbackPricing?: Record<string, number>; // Custom fallback costs
getAuthToken: () => Promise<string | null>; // Auth token provider
}
```
### Responses
```typescript
interface CreditBalance {
credits: number; // Current balance
maxCreditLimit: number; // Max capacity
userId: string;
currency?: string; // Default: 'mana'
lastUpdated?: string;
}
interface CreditCheckResponse {
hasEnoughCredits: boolean;
currentCredits: number;
requiredCredits: number;
deficit?: number; // Amount short (if insufficient)
creditType?: 'user' | 'space';
context?: Record<string, unknown>;
}
interface PricingResponse {
operationCosts: Record<string, number>;
transcriptionPerHour?: number;
lastUpdated: string;
}
```
### Standard Operations
```typescript
type StandardOperationType =
// Memoro operations
| 'TRANSCRIPTION_PER_HOUR'
| 'HEADLINE_GENERATION'
| 'MEMORY_CREATION'
| 'BLUEPRINT_PROCESSING'
| 'QUESTION_MEMO'
| 'NEW_MEMORY'
| 'MEMO_COMBINE'
| 'MEMO_SHARING'
| 'SPACE_OPERATION'
// Maerchenzauber operations
| 'CHARACTER_CREATION'
| 'CHARACTER_GENERATION_FROM_IMAGE'
| 'CHARACTER_IMPORT'
| 'STORY_CREATION'
| 'STORY_CONTINUATION'
// ManaDeck operations
| 'DECK_CREATION'
| 'CARD_GENERATION'
| 'AI_REVIEW'
// Generic operations
| 'AI_PROCESSING'
| 'EXPORT'
| 'IMPORT'
| string; // Custom operations allowed
```
## Key Patterns
### 1. Service Initialization
Initialize on app startup to preload pricing:
```typescript
// In app initialization (e.g., +layout.svelte, App.tsx, main.ts)
import { creditService } from '$lib/services/creditService';
onMount(async () => {
await creditService.initialize();
});
```
### 2. Balance Checking Before Operations
Always check balance before costly operations:
```typescript
async function createStory(data: StoryData) {
// Check balance
const check = await creditService.checkOperationBalance('STORY_CREATION');
if (!check.hasEnoughCredits) {
showInsufficientCreditsModal({
required: check.requiredCredits,
current: check.currentCredits,
deficit: check.deficit
});
return;
}
// Proceed with operation
const result = await api.createStory(data);
// Notify service of consumption
if (result.success) {
creditService.triggerCreditUpdate(check.requiredCredits, 'STORY_CREATION');
}
}
```
### 3. Real-Time Credit Updates
Subscribe to credit changes to update UI:
```typescript
// In Svelte store (creditStore.svelte.ts)
let balance = $state<number>(0);
let isLoading = $state(true);
// Initial load
creditService.getBalance().then(data => {
if (data) balance = data.credits;
isLoading = false;
});
// Listen for updates
creditService.onCreditUpdate((consumed) => {
balance -= consumed;
});
export const credits = {
get current() { return balance; },
get isLoading() { return isLoading; }
};
```
### 4. Pricing Display
Show operation costs in UI:
```typescript
async function loadCosts() {
const costs = await Promise.all([
creditService.getOperationCost('STORY_CREATION'),
creditService.getOperationCost('CHARACTER_CREATION'),
creditService.getOperationCost('STORY_CONTINUATION')
]);
return [
{ action: 'Create Story', cost: costs[0], icon: 'book' },
{ action: 'Create Character', cost: costs[1], icon: 'person' },
{ action: 'Continue Story', cost: costs[2], icon: 'add' }
];
}
```
### 5. Fallback Pricing
Provide app-specific fallback pricing:
```typescript
const creditService = createCreditService({
apiUrl: env.API_URL,
getAuthToken: () => auth.getToken(),
fallbackPricing: {
STORY_CREATION: 10,
CHARACTER_CREATION: 20,
STORY_CONTINUATION: 5,
// Merges with DEFAULT_OPERATION_PRICING
}
});
```
### 6. Caching Strategy
The service implements a 30-minute cache for pricing:
```typescript
// First call: fetches from backend
const pricing1 = await creditService.getPricing(); // HTTP request
// Within 30 minutes: returns cached data
const pricing2 = await creditService.getPricing(); // Cached
// Force refresh
creditService.clearCache();
const pricing3 = await creditService.getPricing(); // HTTP request
```
## Integration Points
### Dependencies
- `@manacore/shared-subscription-types` - Type imports for pricing and usage types
### Consumed By
- All SvelteKit web apps - Client-side credit management
- All Expo mobile apps - React Native credit tracking
- NestJS backends - Server-side credit validation (less common)
### Backend Integration
Expects backend endpoints:
**GET `/auth/credits`** (or configured `balanceEndpoint`)
```typescript
// Request
Authorization: Bearer <token>
// Response
{
credits: number;
maxCreditLimit: number;
userId: string;
}
// OR wrapped:
{
data: {
credits: number;
maxCreditLimit: number;
userId: string;
}
}
```
**GET `/credits/pricing`** (or configured `pricingEndpoint`)
```typescript
// Response
{
operationCosts: {
STORY_CREATION: 10,
CHARACTER_CREATION: 20,
// ... all operation costs
},
transcriptionPerHour?: 120,
lastUpdated: "2025-12-16T12:00:00Z"
}
```
## How to Use
### 1. Setup in SvelteKit App
**Create service instance** (`src/lib/services/creditService.ts`):
```typescript
import { createCreditService } from '@manacore/shared-credit-service';
import { auth } from '$lib/stores/auth';
import { env } from '$env/dynamic/public';
export const creditService = createCreditService({
apiUrl: env.PUBLIC_API_URL,
getAuthToken: () => auth.getToken(),
fallbackPricing: {
// App-specific overrides
}
});
```
**Initialize on app load** (`src/routes/+layout.svelte`):
```svelte
<script lang="ts">
import { onMount } from 'svelte';
import { creditService } from '$lib/services/creditService';
onMount(() => {
creditService.initialize();
});
</script>
```
**Create reactive store** (`src/lib/stores/creditStore.svelte.ts`):
```typescript
import { creditService } from '$lib/services/creditService';
let balance = $state<number>(0);
let maxLimit = $state<number>(0);
let isLoading = $state(true);
async function loadBalance() {
const data = await creditService.getBalance();
if (data) {
balance = data.credits;
maxLimit = data.maxCreditLimit;
}
isLoading = false;
}
// Listen for updates
creditService.onCreditUpdate((consumed) => {
balance -= consumed;
});
export const credits = {
get current() { return balance; },
get max() { return maxLimit; },
get isLoading() { return isLoading; },
load: loadBalance,
refresh: loadBalance
};
```
### 2. Setup in Expo App
**Create service instance** (`src/services/creditService.ts`):
```typescript
import { createCreditService } from '@manacore/shared-credit-service';
import { getAuthToken } from '@/stores/authStore';
import Constants from 'expo-constants';
export const creditService = createCreditService({
apiUrl: Constants.expoConfig?.extra?.apiUrl || '',
getAuthToken: async () => await getAuthToken()
});
```
**Initialize in App.tsx**:
```typescript
import { useEffect } from 'react';
import { creditService } from '@/services/creditService';
export default function App() {
useEffect(() => {
creditService.initialize();
}, []);
return <NavigationContainer>...</NavigationContainer>;
}
```
**Create React hook** (`src/hooks/useCredits.ts`):
```typescript
import { useState, useEffect } from 'react';
import { creditService } from '@/services/creditService';
export function useCredits() {
const [balance, setBalance] = useState(0);
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
// Load initial balance
creditService.getBalance().then(data => {
if (data) setBalance(data.credits);
setIsLoading(false);
});
// Subscribe to updates
const unsubscribe = creditService.onCreditUpdate((consumed) => {
setBalance(prev => prev - consumed);
});
return unsubscribe;
}, []);
return { balance, isLoading };
}
```
### 3. Pre-Operation Balance Check
**In SvelteKit**:
```typescript
async function handleCreateStory() {
const check = await creditService.checkOperationBalance('STORY_CREATION');
if (!check.hasEnoughCredits) {
alert(`Insufficient credits. Need ${check.deficit} more mana.`);
return;
}
// Proceed with API call
const response = await fetch('/api/stories', {
method: 'POST',
body: JSON.stringify(storyData)
});
if (response.ok) {
creditService.triggerCreditUpdate(check.requiredCredits, 'STORY_CREATION');
}
}
```
**In Expo**:
```typescript
async function handleCreateCharacter() {
const check = await creditService.checkOperationBalance('CHARACTER_CREATION');
if (!check.hasEnoughCredits) {
Alert.alert(
'Insufficient Credits',
`You need ${check.deficit} more mana to create a character.`
);
return;
}
// Proceed with API call
const result = await api.createCharacter(characterData);
if (result.success) {
creditService.triggerCreditUpdate(check.requiredCredits, 'CHARACTER_CREATION');
}
}
```
### 4. Displaying Operation Costs
```svelte
<script lang="ts">
import { creditService } from '$lib/services/creditService';
import { CostCard } from '@manacore/shared-subscription-ui';
let costs = $state<CostItem[]>([]);
async function loadCosts() {
const [story, character, continuation] = await Promise.all([
creditService.getOperationCost('STORY_CREATION'),
creditService.getOperationCost('CHARACTER_CREATION'),
creditService.getOperationCost('STORY_CONTINUATION')
]);
costs = [
{ action: 'Create Story', cost: story, icon: 'book-outline' },
{ action: 'Create Character', cost: character, icon: 'person-add-outline' },
{ action: 'Continue Story', cost: continuation, icon: 'add-circle-outline' }
];
}
onMount(loadCosts);
</script>
<CostCard {costs} />
```
## Best Practices
### 1. Always Initialize on App Start
Preload pricing to avoid delays on first operation:
```typescript
creditService.initialize(); // Call in +layout.svelte or App.tsx
```
### 2. Use Sync Methods for UI Display
When showing costs in UI (non-blocking):
```typescript
const cost = creditService.getOperationCostSync('STORY_CREATION');
```
### 3. Use Async Methods for Validation
When validating before operations (needs fresh data):
```typescript
const check = await creditService.checkOperationBalance('STORY_CREATION');
```
### 4. Handle Missing Tokens Gracefully
```typescript
const creditService = createCreditService({
apiUrl: env.API_URL,
getAuthToken: async () => {
try {
return await auth.getToken();
} catch (error) {
console.error('Failed to get auth token:', error);
return null; // Service handles null gracefully
}
}
});
```
### 5. Unsubscribe from Updates
Always clean up subscriptions:
```typescript
// Svelte
onDestroy(() => {
if (unsubscribe) unsubscribe();
});
// React
useEffect(() => {
const unsubscribe = creditService.onCreditUpdate(...);
return unsubscribe;
}, []);
```
### 6. Provide App-Specific Fallbacks
Override defaults for app-specific operations:
```typescript
fallbackPricing: {
...DEFAULT_OPERATION_PRICING, // Include defaults
CUSTOM_APP_OPERATION: 15 // Add custom
}
```
### 7. Cache Management
Clear cache when switching users or after purchases:
```typescript
// After successful purchase
creditService.clearCache();
await creditService.initialize();
```
## Common Tasks
### 1. Adding New Operation Type
```typescript
// In types.ts
export type StandardOperationType =
| 'EXISTING_OPERATION'
// ... other operations
| 'NEW_CUSTOM_OPERATION' // Add here
| string;
// Update DEFAULT_OPERATION_PRICING
export const DEFAULT_OPERATION_PRICING: Record<string, number> = {
// ... existing
NEW_CUSTOM_OPERATION: 25,
};
```
### 2. Custom Error Handling
```typescript
async function safeCheckBalance(operation: string) {
try {
return await creditService.checkOperationBalance(operation);
} catch (error) {
console.error('Balance check failed:', error);
// Return safe default
return {
hasEnoughCredits: false,
currentCredits: 0,
requiredCredits: 0,
deficit: 0
};
}
}
```
### 3. Multi-Operation Cost Calculation
```typescript
async function calculateTotalCost(operations: string[], quantities: number[]) {
const costs = await Promise.all(
operations.map((op, i) => creditService.calculateCost(op, quantities[i]))
);
return costs.reduce((sum, cost) => sum + cost, 0);
}
```
### 4. Conditional Pricing
```typescript
// In component
const cost = $derived.by(async () => {
if (isComplexOperation) {
return await creditService.getOperationCost('COMPLEX_OP');
}
return await creditService.getOperationCost('SIMPLE_OP');
});
```
## Notes
- **Framework Agnostic**: Works in browser (SvelteKit, Expo) and Node.js (NestJS)
- **No Global State**: Each instance is independent, create one per app
- **Singleton Pattern**: Create one instance and export it, reuse across app
- **Offline Support**: Falls back to cached/default pricing when offline
- **Type Safety**: Full TypeScript support with proper error types
- **Extensible**: Easy to add custom operations and pricing
- **Event-Driven**: Notification system allows decoupled UI updates
- **Performance**: 30-minute cache reduces API calls
- **Error Handling**: Graceful degradation on API failures
- **Auth Flexibility**: Supports any auth system via `getAuthToken` callback
- **Response Normalization**: Handles both wrapped and direct API responses
- **Currency Agnostic**: Defaults to 'mana' but supports custom currencies
- **Logging**: Console logs for initialization and errors (development friendly)

View file

@ -0,0 +1,21 @@
# Shared Credit Service - Memory
## Recent Changes
<!-- Document significant changes, decisions, and evolution of the package -->
## Common Issues & Solutions
<!-- Track recurring problems and their solutions -->
## Performance Notes
<!-- Document any performance considerations -->
## Future Improvements
<!-- Track ideas for future enhancements -->
## Migration Notes
<!-- Document breaking changes and migration paths -->

View file

@ -0,0 +1,125 @@
# Shared Errors Agent
## Module Information
**Name:** @manacore/shared-errors
**Path:** packages/shared-errors
**Description:** Go-like error handling system for Manacore backends
**Tech Stack:** TypeScript
**Dependencies:**
- None (core package)
**Peer Dependencies:**
- @nestjs/common >=10.0.0 (optional, for NestJS integration)
## Identity
I am the Shared Errors Agent. I provide a Go-inspired error handling system for the ManaCore monorepo. My purpose is to replace exception-based error handling with explicit Result types, enabling type-safe error handling, better error wrapping with context, and consistent error codes across all backend services.
I follow Go's philosophy: errors are values, not control flow. I provide Result<T> types, error wrapping (like fmt.Errorf), type guards (like errors.Is/As), and standardized error codes with HTTP status mappings.
## Expertise
I specialize in:
### Result Types
- Result<T, E> type for explicit success/failure
- ok() and err() constructors
- Type guards: isOk(), isErr()
- Utility functions: unwrap, map, andThen, match
- AsyncResult<T> for async operations
### Error Classes
- AppError base class with error codes
- Specialized errors: ValidationError, AuthError, NotFoundError, etc.
- Error wrapping with context (Go-style)
- Error chains with cause tracking
- HTTP status code mapping
### Type Guards
- Error type checking (isValidationError, isAuthError, etc.)
- Error code checking (hasErrorCode)
- Error chain traversal (findError, rootCause)
- Retryability checking (isRetryable)
### NestJS Integration
- AppExceptionFilter for automatic error handling
- Converts AppError to proper HTTP responses
- Preserves error codes and context
- Integrates with NestJS exception system
## Code Structure
```
src/
├── types/
│ ├── error-codes.ts # ErrorCode enum and mappings
│ ├── result.ts # Result<T> type and utilities
│ └── index.ts
├── errors/
│ ├── app-error.ts # Base AppError class
│ ├── validation-error.ts # Validation errors
│ ├── auth-error.ts # Authentication errors
│ ├── not-found-error.ts # Resource not found
│ ├── credit-error.ts # Credit/payment errors
│ ├── service-error.ts # Service/internal errors
│ ├── rate-limit-error.ts # Rate limiting
│ ├── network-error.ts # Network/external errors
│ ├── database-error.ts # Database errors
│ └── index.ts
├── guards/
│ ├── type-guards.ts # Type guards for errors
│ └── index.ts
├── utils/
│ ├── wrap.ts # Error wrapping utilities
│ └── index.ts
├── nestjs/
│ ├── app-exception.filter.ts # NestJS exception filter
│ └── index.ts
└── index.ts
```
## How to Use
### In Service Layer
```typescript
import {
Result, ok, err, AsyncResult,
ValidationError, NotFoundError, ServiceError
} from '@manacore/shared-errors';
@Injectable()
export class UserService {
async getUser(id: string): AsyncResult<User> {
if (!isValidUuid(id)) {
return err(ValidationError.invalidInput('id', 'must be a valid UUID'));
}
const user = await this.db.findUser(id);
if (!user) {
return err(new NotFoundError('User', id));
}
return ok(user);
}
}
```
### In Controller Layer
```typescript
import { isOk } from '@manacore/shared-errors';
@Controller('api/users')
export class UsersController {
@Get(':id')
async getUser(@Param('id') id: string) {
const result = await this.service.getUser(id);
if (!isOk(result)) {
throw result.error;
}
return result.value;
}
}
```

View file

@ -0,0 +1,13 @@
# Shared Errors Agent - Memory
## Learnings
<!-- Record important patterns, decisions, and gotchas discovered while working with this package -->
### Common Patterns
### Gotchas
### Recent Changes
### Migration Notes

View file

@ -0,0 +1,295 @@
# Agent: Feedback Service Package
## Module Information
**Package Name:** `@manacore/shared-feedback-service`
**Version:** 1.0.0
**Type:** Service factory library
**Purpose:** Provides a reusable feedback service client for submitting, retrieving, and voting on user feedback across web and mobile apps
## Identity
I am the Feedback Service Agent, responsible for providing a centralized, authenticated HTTP client for feedback operations. I handle API communication with the Mana Core Auth backend, manage authentication tokens, and provide a clean, type-safe interface for feedback functionality.
## Expertise
- Factory pattern for service instantiation
- Authenticated HTTP client implementation
- REST API integration with Mana Core Auth
- Multi-app feedback isolation (appId scoping)
- Feedback CRUD operations
- Voting system (upvote/downvote/toggle)
- Query parameter handling for filtering and pagination
- Error handling and response parsing
## Code Structure
```
src/
├── index.ts # Main entry point, exports and re-exports
├── createFeedbackService.ts # Factory function for service instance
└── types.ts # Configuration types
```
### Core Components
**createFeedbackService** (Factory Function)
- Creates configured service instance per app
- Accepts `FeedbackServiceConfig` configuration object
- Returns `FeedbackService` instance with all methods
- Handles authentication and request headers
**FeedbackServiceConfig** (Configuration)
- `apiUrl`: Base URL for feedback API (typically Mana Core Auth)
- `appId`: Application identifier for multi-app isolation
- `getAuthToken`: Async function to retrieve current auth token
- `feedbackEndpoint`: Optional custom endpoint (default: `/api/v1/feedback`)
**FeedbackService** (Return Type)
- `createFeedback(input)`: Submit new feedback
- `getPublicFeedback(query?)`: Fetch community feedback with filters
- `getMyFeedback()`: Fetch current user's feedback
- `vote(feedbackId)`: Add vote to feedback item
- `unvote(feedbackId)`: Remove vote from feedback item
- `toggleVote(feedbackId, currentlyVoted)`: Toggle vote state
## Key Patterns
### Factory Pattern
```typescript
export const feedbackService = createFeedbackService({
apiUrl: 'https://auth.manacore.app',
appId: 'chat',
getAuthToken: async () => authStore.getToken(),
});
```
### Authenticated Requests
- All requests include `Authorization: Bearer {token}` header
- Custom `X-App-Id` header for app isolation
- Automatic token retrieval via `getAuthToken` callback
- Throws error if user not authenticated
### App Isolation
- Every service instance bound to specific `appId`
- `appId` automatically added to all queries
- Prevents cross-app feedback visibility
- Server-side validation of appId
### Error Handling
```typescript
// Throws on authentication failure
if (!token) throw new Error('Not authenticated');
// Throws on HTTP errors with parsed message
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
throw new Error(errorData.message || `HTTP ${response.status}`);
}
```
### Response Types
- All methods return typed promises from `@manacore/shared-feedback-types`
- `FeedbackResponse`, `FeedbackListResponse`, `VoteResponse`
- Type safety throughout the call chain
## Integration Points
### Dependencies
- `@manacore/shared-feedback-types` - Type definitions
### Consumed By
- `@manacore/shared-feedback-ui` - Svelte components
- Web apps (SvelteKit) - Direct usage in stores/services
- Mobile apps (React Native) - Could be adapted for mobile use
- Any app needing feedback functionality
### Backend Integration
- Connects to Mana Core Auth service
- Default endpoint: `/api/v1/feedback`
- Required endpoints:
- `POST /api/v1/feedback` - Create feedback
- `GET /api/v1/feedback/public` - Get community feedback
- `GET /api/v1/feedback/my` - Get user's feedback
- `POST /api/v1/feedback/:id/vote` - Add vote
- `DELETE /api/v1/feedback/:id/vote` - Remove vote
### Authentication Flow
1. Service method called
2. `getAuthToken()` invoked to get current token
3. Token validation (throws if null/undefined)
4. Request sent with Authorization header
5. Response parsed and typed
6. Error thrown on non-2xx responses
## How to Use
### Basic Setup (SvelteKit)
```typescript
// lib/services/feedback.ts
import { createFeedbackService } from '@manacore/shared-feedback-service';
import { authStore } from '$lib/stores/auth.svelte';
export const feedbackService = createFeedbackService({
apiUrl: import.meta.env.PUBLIC_MANA_CORE_AUTH_URL,
appId: 'chat',
getAuthToken: async () => authStore.token,
});
```
### Creating Feedback
```typescript
try {
const result = await feedbackService.createFeedback({
feedbackText: 'Great app, but needs dark mode!',
category: 'feature',
title: 'Dark Mode Support',
});
if (result.success) {
console.log('Feedback submitted:', result.feedback);
}
} catch (error) {
console.error('Failed to submit:', error);
}
```
### Fetching Feedback
```typescript
// Get community feedback, sorted by votes
const publicFeedback = await feedbackService.getPublicFeedback({
sort: 'votes',
status: 'planned',
limit: 20,
});
// Get user's own feedback
const myFeedback = await feedbackService.getMyFeedback();
```
### Voting
```typescript
// Add vote
await feedbackService.vote(feedbackId);
// Remove vote
await feedbackService.unvote(feedbackId);
// Toggle based on current state
await feedbackService.toggleVote(feedbackId, hasVoted);
```
### Custom Endpoint
```typescript
// For custom backend deployment
const service = createFeedbackService({
apiUrl: 'https://my-backend.com',
appId: 'myapp',
getAuthToken: getToken,
feedbackEndpoint: '/custom/feedback/path',
});
```
### Error Handling Pattern
```typescript
try {
const result = await feedbackService.createFeedback(input);
if (result.success) {
// Handle success
} else {
// Handle business logic error
console.error(result.error);
}
} catch (error) {
// Handle network/auth errors
if (error.message === 'Not authenticated') {
redirectToLogin();
} else {
showErrorToast(error.message);
}
}
```
### Mobile App Integration (Conceptual)
```typescript
// Could be adapted for React Native
import AsyncStorage from '@react-native-async-storage/async-storage';
const feedbackService = createFeedbackService({
apiUrl: 'https://auth.manacore.app',
appId: 'chat-mobile',
getAuthToken: async () => {
return await AsyncStorage.getItem('auth_token');
},
});
```
## Best Practices
### Service Instantiation
- Create ONE instance per app, export as singleton
- Instantiate at module level, not per-component
- Share instance across app via imports
- Don't create new instances in components
### Token Management
- `getAuthToken` should return current valid token
- Handle token refresh in auth layer, not here
- Service will throw if token is null/undefined
- Catch authentication errors and redirect to login
### Error Handling
- Wrap service calls in try-catch blocks
- Distinguish between auth errors and API errors
- Parse response.error for business logic errors
- Show user-friendly messages, log details
### Query Optimization
- Use `limit` and `offset` for pagination
- Filter by `status` or `category` when possible
- `appId` is always set automatically
- Sort by `votes` for community, `recent` for updates
### Response Handling
- Always check `result.success` before accessing data
- Handle both success and error paths
- Don't assume `feedback` or `items` exists
- Type narrowing with TypeScript discriminated unions
### Testing
- Mock `getAuthToken` in tests
- Use test API URL in test environment
- Mock fetch for unit tests
- Integration tests against staging backend
## Common Gotcas
### Authentication
- Service throws synchronously if no token
- Must handle "Not authenticated" error
- Token validation happens on every request
- No token caching within service
### App Isolation
- `appId` cannot be changed per-request
- Create separate instance for different apps
- Server must validate appId matches token
- Cross-app queries not supported
### URL Normalization
- Trailing slashes removed from `apiUrl`
- Full endpoint constructed: `{baseUrl}{endpoint}`
- Query params properly encoded
- Headers set automatically
### Response Parsing
- Non-JSON responses will throw
- Error responses still parsed as JSON
- Empty responses (204) may cause issues
- Always returns typed objects, never raw fetch response
### Vote Idempotency
- Voting same item twice may return error
- Server enforces one-vote-per-user constraint
- `toggleVote` handles current state logic
- Use `userHasVoted` field to track state

View file

@ -0,0 +1,21 @@
# Memory: Feedback Service Package
## Recent Changes
<!-- Track API changes, new endpoints, or configuration updates -->
## Known Issues
<!-- Document bugs, limitations, or edge cases in the service -->
## Decisions Made
<!-- Record architectural decisions about service design -->
## Performance Notes
<!-- Track performance insights, optimization opportunities -->
## Future Considerations
<!-- Ideas for service improvements or new features -->

View file

@ -0,0 +1,196 @@
# Agent: Feedback Types Package
## Module Information
**Package Name:** `@manacore/shared-feedback-types`
**Version:** 1.0.0
**Type:** TypeScript types library
**Purpose:** Provides shared TypeScript types and constants for feedback functionality across the Manacore monorepo
## Identity
I am the Feedback Types Agent, responsible for maintaining type definitions and data models for user feedback collection, categorization, status tracking, and voting functionality. I ensure type safety and consistency across all apps that implement feedback features.
## Expertise
- TypeScript type definitions for feedback domain
- Feedback lifecycle states and categorization
- API contract definitions (request/response types)
- Type-safe constants and configuration objects
- Multi-language label mappings (German locale)
- Feedback metadata structures
## Code Structure
```
src/
├── index.ts # Main entry point, re-exports all types
├── feedback.ts # Core feedback domain types
└── api.ts # API request/response contract types
```
### Core Types (`feedback.ts`)
**FeedbackCategory**
- Union type: `'bug' | 'feature' | 'improvement' | 'question' | 'other'`
- Categorizes user feedback submissions
- Used for filtering and organization
**FeedbackStatus**
- Union type: `'submitted' | 'under_review' | 'planned' | 'in_progress' | 'completed' | 'declined'`
- Tracks feedback lifecycle from submission to resolution
- Maps to visual status badges in UI
**Feedback Interface**
- `id`: Unique identifier
- `userId`: Submitter's user ID
- `appId`: Source application identifier
- `title`: Optional short title
- `feedbackText`: Main feedback content
- `category`: FeedbackCategory type
- `status`: FeedbackStatus type
- `isPublic`: Whether visible in community board
- `adminResponse`: Optional admin reply
- `voteCount`: Number of upvotes
- `userHasVoted`: Whether current user has voted
- `deviceInfo`: Optional device/browser metadata
- `createdAt`, `updatedAt`, `publishedAt`, `completedAt`: Timestamps
**FeedbackVote Interface**
- `id`: Vote identifier
- `feedbackId`: Reference to feedback item
- `userId`: User who voted
- `createdAt`: Vote timestamp
**Constants**
- `FEEDBACK_CATEGORY_LABELS`: German labels for each category
- `FEEDBACK_STATUS_CONFIG`: Status display configuration (label, color, icon)
### API Types (`api.ts`)
**CreateFeedbackInput**
- Input for creating new feedback
- Fields: `title?`, `feedbackText`, `category?`, `deviceInfo?`
**FeedbackQueryParams**
- Query parameters for filtering/pagination
- Fields: `appId?`, `status?`, `category?`, `sort?`, `limit?`, `offset?`
**FeedbackResponse**
- Single feedback item response
- Fields: `success`, `feedback?`, `error?`
**FeedbackListResponse**
- Multiple feedback items response
- Fields: `success`, `items`, `total`, `error?`
**VoteResponse**
- Vote action response
- Fields: `success`, `newVoteCount`, `userHasVoted`, `error?`
## Key Patterns
### Export Strategy
```typescript
// Granular exports for tree-shaking
export {
".": "./src/index.ts",
"./feedback": "./src/feedback.ts",
"./api": "./src/api.ts"
}
```
### Type Safety
- All types are strictly defined with no `any` usage
- Union types for exhaustive categorization
- Optional fields marked with `?` operator
- Record types for configuration objects
### Localization
- German labels in `FEEDBACK_CATEGORY_LABELS`
- German labels in `FEEDBACK_STATUS_CONFIG`
- Color-coded status system for visual feedback
### Metadata Handling
- `deviceInfo` uses `Record<string, unknown>` for flexibility
- ISO 8601 date strings for all timestamps
- Separate timestamps for different lifecycle events
## Integration Points
### Consumed By
- `@manacore/shared-feedback-service` - Uses types for service methods
- `@manacore/shared-feedback-ui` - Uses types in Svelte components
- Backend feedback modules - Uses types for API contracts
- Any app implementing feedback features
### Dependencies
- None (zero dependencies, pure types)
### Type Re-exports
Other packages can import from this package in two ways:
```typescript
// Direct import
import { Feedback, FeedbackStatus } from '@manacore/shared-feedback-types';
// From specific modules
import { Feedback } from '@manacore/shared-feedback-types/feedback';
import { CreateFeedbackInput } from '@manacore/shared-feedback-types/api';
```
## How to Use
### Adding New Types
1. Determine if type belongs to domain (`feedback.ts`) or API (`api.ts`)
2. Add type definition with JSDoc comments
3. Export from module-specific file
4. Re-export from `index.ts` if part of public API
5. Run `pnpm type-check` to validate
### Modifying Existing Types
1. Consider backward compatibility - breaking changes affect all consumers
2. Update type definition
3. Update related constants if applicable (e.g., `FEEDBACK_STATUS_CONFIG`)
4. Search for usages across monorepo before changing
5. Run `pnpm type-check` in root to check all consumers
### Adding New Status/Category
1. Add to union type in `feedback.ts`
2. Add label to `FEEDBACK_CATEGORY_LABELS` or `FEEDBACK_STATUS_CONFIG`
3. Update UI components that render these values
4. Update backend validators
### Best Practices
- Keep types pure and focused on data structure
- Use descriptive JSDoc comments for complex types
- Prefer union types over enums for better type narrowing
- Use `Record<K, V>` for configuration objects
- Maintain German localization consistency
- Keep API types separate from domain types
- Version carefully - breaking changes cascade
### Type Organization
- **Domain types** (`feedback.ts`): Core business logic types
- **API types** (`api.ts`): HTTP request/response contracts
- **Constants**: Defined alongside related types for colocation
- **Re-exports**: Public API exposed through `index.ts`
### Testing Types
```typescript
// Types can be tested via type assertions
import type { Feedback } from '@manacore/shared-feedback-types';
const validFeedback: Feedback = {
id: '123',
userId: 'user-1',
appId: 'chat',
// ... all required fields
};
```
### Common Gotchas
- Remember `deviceInfo` is optional and can be any structure
- `userHasVoted` is computed per-request, not stored
- Status progression is not enforced at type level
- Date fields are strings (ISO 8601), not Date objects
- German labels may need translation for other locales

View file

@ -0,0 +1,21 @@
# Memory: Feedback Types Package
## Recent Changes
<!-- Track significant changes to types and their impacts -->
## Known Issues
<!-- Document any type-related issues or limitations -->
## Decisions Made
<!-- Record important architectural decisions about type design -->
## Usage Notes
<!-- Document insights about how types are used across the monorepo -->
## Future Considerations
<!-- Ideas for type improvements or additions -->

View file

@ -0,0 +1,436 @@
# Agent: Feedback UI Package
## Module Information
**Package Name:** `@manacore/shared-feedback-ui`
**Version:** 1.0.0
**Type:** Svelte 5 component library
**Purpose:** Provides reusable Svelte 5 components for feedback collection, display, and voting across all web apps
## Identity
I am the Feedback UI Agent, responsible for maintaining a consistent, polished user interface for feedback functionality across the Manacore monorepo. I provide ready-to-use Svelte 5 components using runes syntax, with scoped styling and full internationalization support.
## Expertise
- Svelte 5 runes mode ($state, $props, $effect, $derived)
- Component-driven architecture
- Scoped CSS styling with CSS custom properties
- Form validation and submission handling
- Real-time voting interactions with animations
- Tab-based navigation (my feedback vs. community)
- Loading states and error handling
- German localization with customizable labels
- Accessibility (ARIA labels, semantic HTML)
## Code Structure
```
src/
├── index.ts # Main entry point, exports all components
├── FeedbackPage.svelte # Full-page feedback UI with tabs
├── FeedbackForm.svelte # Feedback submission form
├── FeedbackList.svelte # List container for feedback items
├── FeedbackCard.svelte # Individual feedback item display
├── VoteButton.svelte # Upvote button with counter
└── StatusBadge.svelte # Status indicator badge
```
### Components
#### FeedbackPage.svelte (Container)
**Purpose:** Complete feedback page with form, tabs, and lists
**Props:**
- `feedbackService`: Pre-configured service instance (required)
- `appName`: App name for page title (required)
- `currentUserId`: For highlighting user's own feedback
- `pageTitle`, `pageSubtitle`: Customizable headings
- `myFeedbackLabel`, `communityLabel`: Tab labels
- `myFeedbackEmptyMessage`, `communityEmptyMessage`: Empty state text
**Features:**
- Two tabs: "Community" and "My Feedback"
- Collapsible feedback form
- Auto-loads data on mount via `$effect`
- Success message toast
- Loading spinner
- Vote handling with optimistic updates
#### FeedbackForm.svelte
**Purpose:** Form for submitting new feedback
**Props:**
- `onSubmit`: Callback with `CreateFeedbackInput`
- `onCancel`: Optional cancel callback
- `isSubmitting`: Loading state
- `feedbackLabel`, `submitLabel`, `cancelLabel`: Custom labels
- `feedbackPlaceholder`: Textarea placeholder
**Features:**
- Textarea with character counter (max 2000)
- Minimum length validation (10 characters)
- Error display
- Disabled state during submission
- Auto-reset on success
#### FeedbackList.svelte
**Purpose:** Renders list of feedback items
**Props:**
- `items`: Array of Feedback objects
- `currentUserId`: For owner highlighting
- `onVote`: Vote toggle callback
- `votingDisabled`: Disable voting (e.g., on "My Feedback" tab)
- `emptyMessage`: No items text
**Features:**
- Empty state with icon
- Maps to FeedbackCard components
- Passes through vote handler
- Highlights user's own feedback
#### FeedbackCard.svelte
**Purpose:** Individual feedback item display
**Props:**
- `feedback`: Feedback object
- `onVote`: Vote callback
- `showStatus`: Show/hide status badge
- `isOwner`: Highlight as user's feedback
- `votingDisabled`: Disable vote button
**Features:**
- Vote button with count
- Title and text display
- Status badge
- Admin response section
- Owner badge
- Formatted date (German locale)
- Hover effect with shadow
#### VoteButton.svelte
**Purpose:** Upvote button with counter and animation
**Props:**
- `count`: Vote count
- `hasVoted`: Current vote state
- `onToggle`: Click handler
- `disabled`: Disabled state
**Features:**
- Upward arrow icon
- Vote count display
- Active/inactive states
- Click animation (scale effect)
- Color changes on vote
- ARIA labels for accessibility
#### StatusBadge.svelte
**Purpose:** Status indicator with color coding
**Props:**
- `status`: FeedbackStatus value
- `size`: 'sm' | 'md' | 'lg'
**Features:**
- Color-coded badges
- Icon support (requires icon implementation)
- German labels from `FEEDBACK_STATUS_CONFIG`
- Responsive sizing
## Key Patterns
### Svelte 5 Runes
```svelte
<script lang="ts">
// Props with type safety
let { feedbackService, appName }: Props = $props();
// Reactive state
let activeTab = $state<'my' | 'community'>('community');
let myFeedback = $state<Feedback[]>([]);
// Effects for side effects
$effect(() => {
loadFeedback();
});
</script>
```
### Component Composition
```svelte
<FeedbackPage {feedbackService} appName="Chat">
└── <FeedbackForm onSubmit={handleSubmit} />
└── <FeedbackList items={...}>
└── <FeedbackCard feedback={...}>
├── <VoteButton />
└── <StatusBadge />
```
### Styling Strategy
- BEM naming convention: `.component__element--modifier`
- CSS custom properties for theming: `hsl(var(--color-primary))`
- Scoped styles per component
- No external CSS frameworks
- Consistent spacing scale (rem units)
- Mobile-first responsive design
### Error Handling
```svelte
try {
await action();
} catch (error) {
console.error('[ComponentName] Error:', error);
// Don't throw, gracefully degrade
}
```
### Event Handling
- Use `onclick={handler}` not `on:click` (Svelte 5)
- Async handlers with loading states
- Optimistic UI updates
- Error recovery without full page reload
## Integration Points
### Dependencies
- `@manacore/shared-feedback-types` - Type definitions
- `@manacore/shared-feedback-service` - Service client
- `svelte` ^5.0.0 (peer dependency)
### Consumed By
- Web apps (SvelteKit) - Import and use directly
- Any Svelte 5 application in monorepo
### CSS Custom Properties (Theme Integration)
Components expect these CSS variables:
```css
--color-surface
--color-foreground
--color-background
--color-border
--color-muted
--color-muted-foreground
--color-primary
--color-primary-foreground
--color-success
--color-error
--color-input
```
### Service Integration
```typescript
import { feedbackService } from '$lib/services/feedback';
import { FeedbackPage } from '@manacore/shared-feedback-ui';
<FeedbackPage
{feedbackService}
appName="My App"
currentUserId={$authStore.userId}
/>
```
## How to Use
### Installing in a Web App
```bash
# Already available in monorepo workspace
# Just import and use
```
### Full Page Implementation
```svelte
<script lang="ts">
import { FeedbackPage } from '@manacore/shared-feedback-ui';
import { feedbackService } from '$lib/services/feedback';
import { authStore } from '$lib/stores/auth.svelte';
</script>
<FeedbackPage
{feedbackService}
appName="Chat App"
currentUserId={authStore.userId}
pageTitle="Feedback & Vorschläge"
pageSubtitle="Hilf uns, die App zu verbessern"
/>
```
### Individual Component Usage
```svelte
<script lang="ts">
import { FeedbackForm } from '@manacore/shared-feedback-ui';
async function handleSubmit(input) {
await feedbackService.createFeedback(input);
}
</script>
<FeedbackForm
onSubmit={handleSubmit}
feedbackPlaceholder="Was können wir verbessern?"
/>
```
### Custom Styling
```svelte
<div class="custom-container">
<FeedbackPage {feedbackService} appName="App" />
</div>
<style>
.custom-container {
/* Override CSS custom properties */
--color-primary: 210 100% 50%;
--color-surface: 0 0% 100%;
}
</style>
```
### Standalone Components
```svelte
<script lang="ts">
import { FeedbackCard, VoteButton } from '@manacore/shared-feedback-ui';
let feedback = $state<Feedback>({...});
function handleVote(id: string, voted: boolean) {
// Custom vote logic
}
</script>
<FeedbackCard
{feedback}
onVote={handleVote}
isOwner={feedback.userId === currentUserId}
/>
```
### Customizing Labels (i18n)
```svelte
<FeedbackPage
{feedbackService}
appName="App"
pageTitle="Feedback & Suggestions"
pageSubtitle="Help us improve"
myFeedbackLabel="My Feedback"
communityLabel="Community"
myFeedbackEmptyMessage="You haven't submitted any feedback yet"
communityEmptyMessage="No community feedback yet"
/>
```
### Form-Only Integration
```svelte
<script lang="ts">
import { FeedbackForm } from '@manacore/shared-feedback-ui';
let showForm = $state(false);
async function handleSubmit(input) {
await feedbackService.createFeedback(input);
showForm = false;
}
</script>
{#if showForm}
<FeedbackForm
onSubmit={handleSubmit}
onCancel={() => showForm = false}
/>
{/if}
```
## Best Practices
### Component Usage
- Use `FeedbackPage` for full-featured implementation
- Use individual components for custom layouts
- Always pass `feedbackService` instance, never create inside component
- Provide `currentUserId` for proper owner highlighting
### State Management
- Components are stateless where possible
- Parent manages data, passes via props
- Components emit events via callbacks
- No internal service calls in child components (except FeedbackPage)
### Error Handling
- Catch errors in async handlers
- Log to console with component name prefix
- Show user-friendly error messages
- Don't crash on API failures
### Performance
- Use `$effect` for data loading
- Avoid unnecessary re-renders
- Debounce user input if needed
- Optimize list rendering for large datasets
### Accessibility
- Use semantic HTML (button, form, etc.)
- Provide ARIA labels on interactive elements
- Ensure keyboard navigation works
- Maintain focus management
### Styling
- Don't override component internals
- Use CSS custom properties for theming
- Maintain BEM naming convention
- Keep styles scoped to component
### Testing
- Test components in isolation
- Mock `feedbackService` in tests
- Test loading/error/success states
- Verify event emission
## Common Gotchas
### Svelte 5 Syntax
- Use `$state`, not `let` with `$:` reactive declarations
- Use `$props()`, not `export let`
- Use `onclick={handler}`, not `on:click={handler}`
- Use `$effect`, not `onMount` for side effects
### Service Integration
- Create service ONCE at app level, pass down
- Don't create service per component instance
- Service must be configured before component mounts
- Authentication errors handled by service, not component
### CSS Custom Properties
- Components require theme variables to be defined
- Default fallback values provided in HSL format
- Can override at any parent level
- Use HSL format: `hsl(var(--color-primary))`
### Vote State Management
- `userHasVoted` is per-request, updated from server
- Optimistic updates happen in parent (FeedbackPage)
- VoteButton is presentational, doesn't manage state
- Vote count updated after server confirmation
### Form Validation
- Minimum 10 characters enforced
- Maximum 2000 characters enforced
- Client-side validation only, backend must validate
- Form resets on successful submission
### Tab State
- Tab state managed in FeedbackPage
- Switching tabs doesn't reload data
- Data loaded once on mount
- Manual refresh needed for updates
### Date Formatting
- Uses German locale by default
- Format: DD.MM.YYYY
- Can be customized by overriding `formatDate` function
- Dates are ISO strings from API
### Empty States
- Customizable empty messages per tab
- Icon SVG embedded in component
- Center-aligned with padding
- Shows when `items.length === 0`

View file

@ -0,0 +1,21 @@
# Memory: Feedback UI Package
## Recent Changes
<!-- Track component updates, style changes, new features -->
## Known Issues
<!-- Document UI bugs, browser compatibility issues, accessibility gaps -->
## Decisions Made
<!-- Record design decisions, UX patterns, component architecture choices -->
## Design Patterns
<!-- Document reusable patterns and component composition strategies -->
## Future Considerations
<!-- Ideas for new components, UX improvements, accessibility enhancements -->

View file

@ -0,0 +1,263 @@
# Shared Help Content Expert
## Module: @manacore/shared-help-content
**Path:** `packages/shared-help-content`
**Description:** Content loading, parsing, and search utilities for the help/documentation system. Parses Markdown files with YAML frontmatter, validates with Zod schemas, converts to HTML, and provides full-text search with Fuse.js.
**Tech Stack:** TypeScript, gray-matter, marked, Fuse.js
**Key Dependencies:** @manacore/shared-help-types, gray-matter 4.0+, marked 15.0+, fuse.js 7.0+
## Identity
You are the **Shared Help Content Expert**. You have deep knowledge of:
- Markdown parsing with YAML frontmatter extraction
- Zod schema validation for content integrity
- HTML rendering from Markdown using marked
- Full-text search implementation with Fuse.js
- Content merging strategies (central + app-specific)
- Multi-language content loading
## Expertise
- Parsing Markdown files with gray-matter
- Validating frontmatter with Zod schemas
- Converting Markdown to HTML with marked
- Building searchable indexes with Fuse.js
- Merging central and app-specific content
- Extracting structured data from Markdown (tables, steps)
## Code Structure
```
packages/shared-help-content/src/
├── parser.ts # Markdown + frontmatter parsing, HTML rendering
├── loader.ts # Content loaders for each content type
├── merger.ts # Merge central and app-specific content
├── search.ts # Fuse.js search index and search functions
└── index.ts # Public exports
```
## Key Patterns
### Markdown Parsing
Parse Markdown with validated frontmatter:
```typescript
const parsed = parseMarkdown(rawContent, faqFrontmatterSchema);
// Returns: { frontmatter: T, content: string, html: string }
```
### Content Loading
Load from file map (path -> content):
```typescript
const content = loadHelpContentFromFiles(
{
'content/faq/en/login.md': '...',
'content/features/en/chat.md': '...',
},
{ locale: 'en', fallbackLocale: 'en' }
);
```
### Search Index
Build searchable index with Fuse.js:
```typescript
const index = buildSearchIndex(content, {
titleWeight: 2,
contentWeight: 1,
tagsWeight: 1.5,
threshold: 0.3,
});
const results = search(index, 'login', content, { limit: 10 });
```
### Content Merging
Merge central and app-specific content:
```typescript
const merged = mergeContent(centralContent, appContent, {
appId: 'chat',
locale: 'en',
overrideById: true, // App content replaces central by ID
});
```
## Parser Module (`parser.ts`)
### Main Functions
- `parseMarkdown<T>(raw, schema?, options?)` - Parse with frontmatter validation
- `parseMarkdownFiles<T>(files, schema?, options?)` - Batch parsing
- `stripHtml(html)` - Remove HTML tags for search indexing
- `generateExcerpt(content, maxLength)` - Create text excerpts
### Example
```typescript
import { parseMarkdown, faqFrontmatterSchema } from '@manacore/shared-help-content';
const parsed = parseMarkdown(markdownText, faqFrontmatterSchema);
console.log(parsed.frontmatter.question); // Validated FAQ data
console.log(parsed.html); // Rendered HTML
```
## Loader Module (`loader.ts`)
### Content Type Parsers
Each content type has a dedicated parser:
- `parseFAQContent(raw)` - Returns `FAQItem`
- `parseFeatureContent(raw)` - Returns `FeatureItem`
- `parseShortcutsContent(raw)` - Returns `ShortcutsItem` (parses tables)
- `parseGettingStartedContent(raw)` - Returns `GettingStartedItem` (extracts steps)
- `parseChangelogContent(raw)` - Returns `ChangelogItem`
- `parseContactContent(raw)` - Returns `ContactInfo`
### Path-Based Loading
`loadHelpContentFromFiles` detects content type from file path:
- `/faq/` -> FAQ items
- `/features/` -> Feature items
- `/shortcuts/` -> Shortcuts
- `/getting-started/` -> Getting started guides
- `/changelog/` -> Changelog entries
- `/contact/` -> Contact info
### Special Parsing
#### Shortcuts Table Parser
Extracts keyboard shortcuts from Markdown tables:
```markdown
| Shortcut | Action | Description |
|----------|--------|-------------|
| Cmd+N | New | Create new |
```
#### Guide Steps Parser
Extracts steps from h2 headers:
```markdown
## Step 1: Setup
Instructions here...
## Step 2: Configure
More instructions...
```
## Merger Module (`merger.ts`)
### Functions
- `mergeContent(central, app, options)` - Merge two HelpContent objects
- `createEmptyContent()` - Empty HelpContent template
### Merge Strategy
```typescript
interface MergeContentOptions {
appId: string;
locale: SupportedLanguage;
overrideById?: boolean; // If true, app content replaces central by ID
}
```
## Search Module (`search.ts`)
### Search Index Configuration
```typescript
interface SearchIndexConfig {
titleWeight?: number; // Default: 2
contentWeight?: number; // Default: 1
tagsWeight?: number; // Default: 1.5
threshold?: number; // Default: 0.3 (0=exact, 1=anything)
minMatchCharLength?: number; // Default: 2
}
```
### Search Functions
- `buildSearchIndex(content, config?)` - Create Fuse.js index
- `search(index, query, content, options?)` - Execute search
- `createSearcher(content, config?)` - Returns search function
- `flattenContentForSearch(content)` - Convert to searchable items
### Search Options
```typescript
interface SearchOptions {
limit?: number; // Max results (default: 10)
threshold?: number; // Override index threshold
types?: ('faq' | 'feature' | 'guide' | 'changelog')[];
appId?: string; // Filter by app
}
```
### Simple Search Pattern
```typescript
const searcher = createSearcher(content);
const results = searcher('authentication', { limit: 5 });
```
## Package Exports
### Main Export
```typescript
export * from './parser.js';
export * from './loader.js';
export * from './merger.js';
export * from './search.js';
```
### Subpath Exports
- `@manacore/shared-help-content` - All utilities
- `@manacore/shared-help-content/loader` - Loading utilities
- `@manacore/shared-help-content/parser` - Parsing utilities
- `@manacore/shared-help-content/search` - Search utilities
- `@manacore/shared-help-content/merger` - Merging utilities
## Integration Points
- **Used by:**
- `@manacore/shared-help-mobile` - Mobile apps load and search content
- `@manacore/shared-help-ui` - Web apps load and search content
- Backend services for pre-processing content
- **Depends on:**
- `@manacore/shared-help-types` - Type definitions and schemas
- gray-matter - Frontmatter parsing
- marked - Markdown to HTML
- fuse.js - Full-text search
## Common Tasks
### Load Content from Files
```typescript
import { loadHelpContentFromFiles } from '@manacore/shared-help-content';
const files = {
'content/faq/en/login.md': await fs.readFile('...'),
'content/features/en/chat.md': await fs.readFile('...'),
};
const content = loadHelpContentFromFiles(files, {
locale: 'en',
fallbackLocale: 'en',
});
```
### Implement Search
```typescript
import { createSearcher } from '@manacore/shared-help-content';
const search = createSearcher(helpContent, {
titleWeight: 2,
threshold: 0.3,
});
const results = search('how to login', { limit: 10 });
```
### Merge App-Specific Content
```typescript
import { mergeContent } from '@manacore/shared-help-content';
const finalContent = mergeContent(centralContent, chatAppContent, {
appId: 'chat',
locale: 'en',
overrideById: true,
});
```
## Error Handling
- Parsers throw on invalid frontmatter (Zod validation)
- `loadHelpContentFromFiles` silently skips unparseable files
- Search functions return empty arrays on error
## How to Use
```
"Read packages/shared-help-content/.agent/ and help me with..."
```

View file

@ -0,0 +1,16 @@
# Memory: @manacore/shared-help-content
## Recent Changes
<!-- Track significant updates to parsing, loading, or search -->
## Known Issues
<!-- Document any parsing or search issues -->
## Decisions Made
<!-- Record important architectural decisions -->
## Performance Notes
<!-- Track search performance and optimization notes -->
## Integration Notes
<!-- Notes about integration with other packages -->

View file

@ -0,0 +1,395 @@
# Shared Help Mobile Expert
## Module: @manacore/shared-help-mobile
**Path:** `packages/shared-help-mobile`
**Description:** React Native components for the help/documentation system in mobile apps. Provides a complete help screen with FAQ, features, shortcuts, guides, changelog, and contact sections. Built with Expo and styled with NativeWind.
**Tech Stack:** React Native, Expo SDK 52+, NativeWind 4.0+, TypeScript
**Key Dependencies:** @manacore/shared-help-types, @manacore/shared-help-content
## Identity
You are the **Shared Help Mobile Expert**. You have deep knowledge of:
- React Native component architecture for help systems
- NativeWind styling (Tailwind CSS for React Native)
- Mobile-optimized help content presentation
- Search UX patterns for mobile
- Category-based content navigation
- Expo best practices
## Expertise
- Building mobile help screens with React Native
- Implementing expandable FAQ lists
- Search functionality with real-time results
- Category tabs for content navigation
- Mobile-friendly contact cards
- Performance optimization for content-heavy screens
## Code Structure
```
packages/shared-help-mobile/src/
├── screens/
│ └── HelpScreen.tsx # Main help screen component
├── components/
│ ├── FAQList.tsx # FAQ list with categories
│ ├── FAQItem.tsx # Single FAQ item (expandable)
│ ├── FeaturesList.tsx # Features grid/list
│ ├── FeatureCard.tsx # Single feature card
│ ├── HelpSearchBar.tsx # Search input with debounce
│ ├── CategoryTabs.tsx # Horizontal scrollable tabs
│ └── ContactCard.tsx # Support contact information
├── hooks/
│ └── useHelpContent.ts # Content loading and search hooks
├── types.ts # Component prop types
└── index.ts # Public exports
```
## Key Patterns
### Main Screen Component
```typescript
import { HelpScreen } from '@manacore/shared-help-mobile';
<HelpScreen
content={helpContent}
appName="Chat"
appId="chat"
translations={translations}
defaultSection="faq"
/>
```
### Hook-Based Content Loading
```typescript
const { content, loading, error } = useHelpContent({
appId: 'chat',
locale: 'en',
centralContent,
appContent,
});
```
### Search Hook
```typescript
const { search } = useHelpSearch(content);
const results = search('authentication', 10);
```
## HelpScreen Component
### Props
```typescript
interface HelpScreenProps {
content: HelpContent;
appName: string;
appId: string;
translations: HelpTranslations;
onBack?: () => void;
defaultSection?: HelpSection;
}
type HelpSection =
| 'faq'
| 'features'
| 'shortcuts'
| 'getting-started'
| 'changelog'
| 'contact';
```
### Features
- Search bar with real-time filtering
- Category tabs (auto-hidden during search)
- Section-based navigation
- Expandable FAQ items
- Search results with excerpts
- Empty states for each section
### Layout
```
┌─────────────────────────┐
│ Help │
│ Get support for Chat │
├─────────────────────────┤
│ 🔍 Search help... │
├─────────────────────────┤
│ [FAQ] Features Shortcuts│ ← Category tabs
├─────────────────────────┤
│ Content area │
│ │
│ (FAQ list, features, │
│ or search results) │
└─────────────────────────┘
```
## Components
### FAQList
Displays FAQ items with category filtering:
```typescript
<FAQList
items={content.faq}
translations={translations}
showCategories={true}
maxItems={10}
expandFirst={false}
/>
```
### FAQItem
Single expandable FAQ with animation:
```typescript
<FAQItem
item={faqItem}
expanded={isExpanded}
onToggle={() => setExpanded(!isExpanded)}
/>
```
### FeaturesList
Grid or list of feature cards:
```typescript
<FeaturesList
items={content.features}
translations={translations}
layout="grid"
/>
```
### FeatureCard
Single feature with icon and description:
```typescript
<FeatureCard
feature={featureItem}
onPress={() => handleFeaturePress(featureItem)}
/>
```
### HelpSearchBar
Search input with clear button:
```typescript
<HelpSearchBar
placeholder="Search help..."
onSearch={handleSearch}
onClear={handleClear}
debounce={300}
/>
```
### CategoryTabs
Horizontal scrollable tabs:
```typescript
<CategoryTabs
sections={sections}
activeSection={activeSection}
onSectionChange={setActiveSection}
/>
```
### ContactCard
Support contact information:
```typescript
<ContactCard
contact={content.contact}
translations={translations}
onEmailPress={handleEmail}
onUrlPress={handleUrl}
/>
```
## Hooks
### useHelpContent
Load and merge help content:
```typescript
interface UseHelpContentOptions {
appId: string;
locale: SupportedLanguage;
centralContent?: HelpContent;
appContent?: HelpContent;
}
interface UseHelpContentResult {
content: HelpContent;
loading: boolean;
error: Error | null;
}
```
Usage:
```typescript
const { content, loading, error } = useHelpContent({
appId: 'chat',
locale: 'en',
centralContent: centralHelpContent,
appContent: chatSpecificContent,
});
```
### useHelpSearch
Search help content:
```typescript
const { search } = useHelpSearch(content);
// Returns SearchResult[]
const results = search(query, limit);
```
## Styling with NativeWind
### Dark Mode Support
All components support dark mode via NativeWind:
```typescript
<View className="bg-white dark:bg-gray-800">
<Text className="text-gray-900 dark:text-gray-100">
Content
</Text>
</View>
```
### Common Class Patterns
```typescript
// Cards
"bg-white dark:bg-gray-800 rounded-xl p-4 border border-gray-200 dark:border-gray-700"
// Buttons
"px-4 py-2 bg-primary-500 dark:bg-primary-600 rounded-lg"
// Text
"text-gray-900 dark:text-gray-100"
"text-gray-600 dark:text-gray-400"
```
## Translations Interface
### Required Translations
```typescript
interface HelpTranslations {
title: string;
subtitle?: string;
searchPlaceholder: string;
sections: {
faq: string;
features: string;
shortcuts: string;
gettingStarted: string;
changelog: string;
contact: string;
};
search: {
noResults: string;
resultsCount: string;
};
faq: {
noItems: string;
categories: Record<FAQCategory, string>;
};
features: {
noItems: string;
};
shortcuts: {
noItems: string;
};
gettingStarted: {
noItems: string;
};
changelog: {
noItems: string;
};
common: {
back: string;
showMore: string;
showLess: string;
};
}
```
## Integration Points
- **Used by:**
- Mobile apps (Expo/React Native) for help screens
- App-specific help implementations
- **Depends on:**
- `@manacore/shared-help-types` - Type definitions
- `@manacore/shared-help-content` - Content loading and search
- React Native - UI framework
- Expo - Mobile framework
- NativeWind - Styling
## Peer Dependencies
```json
{
"expo": ">=52.0.0",
"nativewind": "^4.0.0",
"react": "18.3.1",
"react-native": ">=0.76.0"
}
```
## Common Tasks
### Implement Help Screen
```typescript
import { HelpScreen } from '@manacore/shared-help-mobile';
import { useHelpContent } from '@manacore/shared-help-mobile';
export function AppHelpScreen() {
const { content, loading } = useHelpContent({
appId: 'chat',
locale: 'en',
centralContent: globalHelpContent,
});
if (loading) return <LoadingSpinner />;
return (
<HelpScreen
content={content}
appName="Chat"
appId="chat"
translations={translations}
defaultSection="faq"
onBack={() => navigation.goBack()}
/>
);
}
```
### Custom Search Implementation
```typescript
import { useHelpSearch } from '@manacore/shared-help-mobile';
function CustomSearchScreen({ content }) {
const { search } = useHelpSearch(content);
const [query, setQuery] = useState('');
const [results, setResults] = useState([]);
function handleSearch(q: string) {
setQuery(q);
if (q.trim().length >= 2) {
setResults(search(q, 10));
} else {
setResults([]);
}
}
return (
<View>
<HelpSearchBar onSearch={handleSearch} />
{results.map(result => (
<SearchResultCard key={result.id} result={result} />
))}
</View>
);
}
```
## Type Safety Note
Type-checking is skipped for this package during build:
```json
"type-check": "echo 'Skipping type-check: requires React Native environment'"
```
This is because React Native types require the full RN environment.
## How to Use
```
"Read packages/shared-help-mobile/.agent/ and help me with..."
```

View file

@ -0,0 +1,16 @@
# Memory: @manacore/shared-help-mobile
## Recent Changes
<!-- Track significant updates to mobile components -->
## Known Issues
<!-- Document any mobile UI or performance issues -->
## Decisions Made
<!-- Record important architectural decisions -->
## Performance Notes
<!-- Track mobile performance considerations -->
## Integration Notes
<!-- Notes about integration with React Native and Expo -->

View file

@ -0,0 +1,175 @@
# Shared Help Types Expert
## Module: @manacore/shared-help-types
**Path:** `packages/shared-help-types`
**Description:** TypeScript type definitions and Zod validation schemas for the help/documentation system. Defines the structure for FAQ items, features, keyboard shortcuts, getting started guides, changelogs, and contact information across all apps.
**Tech Stack:** TypeScript, Zod
**Key Dependencies:** Zod 3.24+
## Identity
You are the **Shared Help Types Expert**. You have deep knowledge of:
- Type-safe help content structures with strong typing
- Zod schema validation for Markdown frontmatter
- Multi-language support with locale types
- Content categorization and organization patterns
- Search and filtering type definitions
## Expertise
- Defining content item types (FAQ, Features, Shortcuts, Guides, Changelog, Contact)
- Creating Zod schemas for runtime validation of Markdown frontmatter
- Multi-language content with `SupportedLanguage` type
- App-specific vs global content patterns
- Search result types and configuration
## Code Structure
```
packages/shared-help-types/src/
├── content.ts # Core content type definitions (HelpContent, FAQItem, etc.)
├── schemas.ts # Zod schemas for frontmatter validation
├── search.ts # Search-related types (SearchResult, SearchOptions)
└── index.ts # Public exports
```
## Key Patterns
### Base Content Item
All content items extend `BaseContentItem`:
```typescript
interface BaseContentItem {
id: string;
language: SupportedLanguage;
order?: number;
appSpecific?: boolean; // If true, only shown for specific apps
apps?: string[]; // List of app IDs this content applies to
lastUpdated?: Date;
}
```
### Content Types
Six main content types:
- **FAQItem**: Questions and answers with categories
- **FeatureItem**: Feature descriptions with highlights
- **ShortcutsItem**: Keyboard shortcuts grouped by category
- **GettingStartedItem**: Step-by-step guides with difficulty levels
- **ChangelogItem**: Release notes with versioning
- **ContactInfo**: Support contact information
### Zod Validation
Schemas validate Markdown frontmatter:
```typescript
const faqFrontmatterSchema = baseContentSchema.extend({
question: z.string().min(1),
category: faqCategorySchema,
featured: z.boolean().optional().default(false),
tags: z.array(z.string()).optional().default([]),
});
```
## Supported Languages
```typescript
type SupportedLanguage = 'en' | 'de' | 'fr' | 'it' | 'es';
```
## Content Categories
### FAQ Categories
`'general' | 'account' | 'billing' | 'features' | 'technical' | 'privacy'`
### Feature Categories
`'getting-started' | 'core' | 'advanced' | 'integration'`
### Shortcut Categories
`'navigation' | 'editing' | 'general' | 'app-specific'`
### Guide Difficulty
`'beginner' | 'intermediate' | 'advanced'`
### Changelog Types
`'major' | 'minor' | 'patch' | 'beta'`
## Aggregated Types
### HelpContent
Container for all help content:
```typescript
interface HelpContent {
faq: FAQItem[];
features: FeatureItem[];
shortcuts: ShortcutsItem[];
gettingStarted: GettingStartedItem[];
changelog: ChangelogItem[];
contact: ContactInfo | null;
}
```
### Search Types
```typescript
interface SearchResult {
id: string;
type: 'faq' | 'feature' | 'guide' | 'changelog';
title: string;
excerpt: string;
score: number;
highlight: string;
item: FAQItem | FeatureItem | GettingStartedItem | ChangelogItem;
}
```
## Package Exports
### Main Export
```typescript
export * from './content.js';
export * from './schemas.js';
export * from './search.js';
```
### Subpath Exports
- `@manacore/shared-help-types` - All types
- `@manacore/shared-help-types/content` - Content types only
- `@manacore/shared-help-types/schemas` - Zod schemas only
- `@manacore/shared-help-types/search` - Search types only
## Integration Points
- **Used by:**
- `@manacore/shared-help-content` - Content parsing and loading
- `@manacore/shared-help-mobile` - Mobile UI components
- `@manacore/shared-help-ui` - Web UI components
- **Depends on:** Zod for schema validation
## Common Patterns
### App-Specific Content
Content can be marked as app-specific:
```typescript
const faqItem: FAQItem = {
id: 'chat-threading',
language: 'en',
appSpecific: true,
apps: ['chat'], // Only shown in chat app
question: 'How do I use threading?',
answer: '...',
category: 'features',
};
```
### Multi-Language Support
Same content ID in different languages:
```typescript
const faqEN: FAQItem = { id: 'login', language: 'en', ... };
const faqDE: FAQItem = { id: 'login', language: 'de', ... };
```
### Changelog Changes Structure
```typescript
changes: {
features: [{ title: 'New feature', description: '...' }],
improvements: [{ title: 'Better performance', ... }],
bugfixes: [{ title: 'Fixed crash', ... }],
}
```
## How to Use
```
"Read packages/shared-help-types/.agent/ and help me with..."
```

View file

@ -0,0 +1,16 @@
# Memory: @manacore/shared-help-types
## Recent Changes
<!-- Track significant updates to types and schemas -->
## Known Issues
<!-- Document any type-related issues or limitations -->
## Decisions Made
<!-- Record important architectural decisions -->
## Performance Notes
<!-- Track any performance considerations -->
## Integration Notes
<!-- Notes about integration with other packages -->

View file

@ -0,0 +1,529 @@
# Shared Help UI Expert
## Module: @manacore/shared-help-ui
**Path:** `packages/shared-help-ui`
**Description:** Svelte 5 components for the help/documentation system in web apps. Provides a complete help page with FAQ, features, keyboard shortcuts, getting started guides, changelog, and contact sections. Built with Svelte 5 runes mode and Tailwind CSS.
**Tech Stack:** Svelte 5 (runes mode), TypeScript, Tailwind CSS
**Key Dependencies:** @manacore/shared-help-types, @manacore/shared-help-content, @manacore/shared-icons
## Identity
You are the **Shared Help UI Expert**. You have deep knowledge of:
- Svelte 5 runes mode (`$state`, `$derived`, `$props`, `$effect`)
- Building accessible help documentation UIs
- Interactive components (expandable FAQs, tabbed navigation)
- Search UX with real-time filtering
- Keyboard shortcuts display
- Dark mode with Tailwind CSS
- Type-safe component props
## Expertise
- Svelte 5 component patterns with runes
- Reactive state management with `$state` and `$derived`
- Accessible, semantic HTML for documentation
- Search implementation with live results
- Tab navigation and section switching
- Expandable/collapsible content
- Dark mode styling
## Code Structure
```
packages/shared-help-ui/src/
├── pages/
│ └── HelpPage.svelte # Main help page component
├── components/
│ ├── FAQSection.svelte # FAQ list with categories
│ ├── FAQItem.svelte # Single FAQ item (expandable)
│ ├── FeaturesOverview.svelte # Features grid
│ ├── FeatureCard.svelte # Single feature card
│ ├── KeyboardShortcuts.svelte # Shortcuts table
│ ├── GettingStartedGuide.svelte# Step-by-step guides
│ ├── ChangelogSection.svelte # Release notes
│ ├── ChangelogEntry.svelte # Single changelog entry
│ ├── ContactSection.svelte # Support contact info
│ └── HelpSearch.svelte # Search component
├── types.ts # Component prop types
└── index.ts # Public exports
```
## Key Patterns
### Svelte 5 Runes
All components use Svelte 5 runes syntax:
```svelte
<script lang="ts">
let { content, translations }: HelpPageProps = $props();
let activeSection = $state<HelpSection>('faq');
let searchQuery = $state('');
const filteredFAQs = $derived(() => {
return content.faq.filter(faq =>
faq.question.toLowerCase().includes(searchQuery.toLowerCase())
);
});
$effect(() => {
console.log('Section changed:', activeSection);
});
</script>
```
### Main Page Component
```svelte
import { HelpPage } from '@manacore/shared-help-ui';
<HelpPage
content={helpContent}
appName="Chat"
appId="chat"
translations={translations}
searchEnabled={true}
defaultSection="faq"
showBackButton={false}
onSectionChange={(section) => console.log(section)}
/>
```
## HelpPage Component
### Props
```typescript
interface HelpPageProps {
content: HelpContent;
appName: string;
appId: string;
translations: HelpPageTranslations;
searchEnabled?: boolean; // Default: true
showFAQ?: boolean; // Default: true
showFeatures?: boolean; // Default: true
showShortcuts?: boolean; // Default: true
showGettingStarted?: boolean; // Default: true
showChangelog?: boolean; // Default: true
showContact?: boolean; // Default: true
defaultSection?: HelpSection; // Default: 'faq'
showBackButton?: boolean; // Default: false
onBack?: () => void;
onSectionChange?: (section: HelpSection) => void;
onSearch?: (query: string, results: SearchResult[]) => void;
}
```
### Features
- Search bar with live filtering
- Tab navigation between sections
- Expandable FAQ items
- Category filtering for FAQs
- Keyboard shortcuts table
- Changelog entries with version badges
- Contact information with links
- Empty states for each section
- Dark mode support
### Layout
```
┌─────────────────────────────────┐
│ Help & Support │
│ Get support for Chat │
├─────────────────────────────────┤
│ 🔍 Search help articles... │
├─────────────────────────────────┤
│ [FAQ] Features Shortcuts Guide │ ← Tabs
├─────────────────────────────────┤
│ Content area │
│ │
│ (FAQ, features, shortcuts, etc.)│
└─────────────────────────────────┘
```
## Section Components
### FAQSection
Displays FAQ items with category filtering:
```svelte
<FAQSection
items={content.faq}
translations={translations}
showCategories={true}
maxItems={10}
expandFirst={true}
/>
```
**Features:**
- Category pills for filtering
- Expandable/collapsible items
- First item auto-expanded (optional)
- Show more/less button
- Empty state
### FAQItem
Single expandable FAQ:
```svelte
<FAQItem
item={faqItem}
expanded={isExpanded}
onToggle={() => expanded = !expanded}
/>
```
**Features:**
- Smooth expand/collapse animation
- Rendered HTML content
- Accessibility (ARIA attributes)
### FeaturesOverview
Grid of feature cards:
```svelte
<FeaturesOverview
items={content.features}
translations={translations}
columns={3}
/>
```
**Features:**
- Responsive grid layout
- Category grouping
- Icon support
- "Coming soon" badges
- Empty state
### FeatureCard
Single feature card:
```svelte
<FeatureCard
feature={featureItem}
onLearnMore={(url) => window.open(url)}
/>
```
**Features:**
- Icon display
- Highlights as badges
- "Learn more" link
- Availability status
### KeyboardShortcuts
Table of keyboard shortcuts:
```svelte
<KeyboardShortcuts
items={content.shortcuts}
translations={translations}
groupByCategory={true}
/>
```
**Features:**
- Category grouping
- Platform-specific shortcuts (Cmd/Ctrl)
- Styled keyboard keys
- Empty state
### GettingStartedGuide
Step-by-step guides:
```svelte
<GettingStartedGuide
items={content.gettingStarted}
translations={translations}
showSteps={true}
/>
```
**Features:**
- Difficulty badges (beginner/intermediate/advanced)
- Estimated time display
- Step numbering
- Prerequisites list
- Empty state
### ChangelogSection
Release notes list:
```svelte
<ChangelogSection
items={content.changelog}
translations={translations}
maxEntries={10}
/>
```
**Features:**
- Version badges (major/minor/patch/beta)
- Release date formatting
- Categorized changes (features/improvements/bugfixes)
- Platform badges
- Empty state
### ChangelogEntry
Single changelog entry:
```svelte
<ChangelogEntry
entry={changelogItem}
expanded={isExpanded}
onToggle={() => expanded = !expanded}
/>
```
**Features:**
- Type badges (major, minor, patch, beta)
- Categorized changes
- Platform support indicators
### ContactSection
Support contact information:
```svelte
<ContactSection
contact={content.contact}
translations={translations}
/>
```
**Features:**
- Email links (mailto:)
- Support URL
- Social links (Discord, Twitter)
- Documentation link
- Response time display
- Empty state
### HelpSearch
Search component with live results:
```svelte
<HelpSearch
content={helpContent}
translations={translations}
placeholder="Search help..."
onResultSelect={(result) => handleSelect(result)}
/>
```
**Features:**
- Real-time search with Fuse.js
- Result excerpts with highlighting
- Type badges (FAQ, Feature, Guide, Changelog)
- Click to navigate to section
- Empty state for no results
## Styling with Tailwind CSS
### Dark Mode Support
All components use Tailwind dark mode classes:
```svelte
<div class="bg-white dark:bg-gray-900">
<h1 class="text-gray-900 dark:text-gray-100">Title</h1>
<p class="text-gray-600 dark:text-gray-400">Description</p>
</div>
```
### Common Class Patterns
```css
/* Cards */
.card {
@apply bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700;
}
/* Buttons */
.btn-primary {
@apply bg-primary-500 hover:bg-primary-600 dark:bg-primary-600 dark:hover:bg-primary-700;
}
/* Text */
.text-heading {
@apply text-gray-900 dark:text-gray-100;
}
.text-body {
@apply text-gray-600 dark:text-gray-400;
}
```
### Primary Color
Components use `primary-*` colors that should be defined in Tailwind config:
```javascript
// tailwind.config.js
theme: {
extend: {
colors: {
primary: {
50: '#...',
// ... through 950
}
}
}
}
```
## Translations Interface
### Required Translations
```typescript
interface HelpPageTranslations {
title: string;
subtitle?: string;
searchPlaceholder: string;
sections: {
faq: string;
features: string;
shortcuts: string;
gettingStarted: string;
changelog: string;
contact: string;
};
faq: {
noItems: string;
categories: Record<FAQCategory, string>;
};
features: {
noItems: string;
comingSoon: string;
learnMore: string;
};
shortcuts: {
noItems: string;
};
gettingStarted: {
noItems: string;
difficulty: Record<GuideDifficulty, string>;
estimatedTime: string;
prerequisites: string;
};
changelog: {
noItems: string;
releaseDate: string;
};
contact: {
noContact: string;
supportEmail: string;
supportUrl: string;
responseTime: string;
};
common: {
back: string;
showMore: string;
showLess: string;
};
search: {
noResults: string;
resultsCount: string;
searching: string;
};
}
```
## Package Exports
### Main Exports
```typescript
export { default as HelpPage } from './pages/HelpPage.svelte';
export { default as FAQSection } from './components/FAQSection.svelte';
export { default as FAQItem } from './components/FAQItem.svelte';
// ... all components
export type { HelpPageProps, HelpPageTranslations, ... } from './types.js';
```
### Subpath Exports
Individual components can be imported via subpaths:
```typescript
import HelpPage from '@manacore/shared-help-ui/HelpPage.svelte';
import FAQSection from '@manacore/shared-help-ui/FAQSection.svelte';
```
## Integration Points
- **Used by:**
- SvelteKit web apps for help pages
- App-specific help implementations
- **Depends on:**
- `@manacore/shared-help-types` - Type definitions
- `@manacore/shared-help-content` - Content loading and search
- `@manacore/shared-icons` - Icon components
- Svelte 5 - UI framework
## Peer Dependencies
```json
{
"svelte": "^5.0.0"
}
```
## Common Tasks
### Basic Help Page
```svelte
<script lang="ts">
import { HelpPage } from '@manacore/shared-help-ui';
import type { HelpContent } from '@manacore/shared-help-types';
let helpContent: HelpContent = $props();
const translations = {
title: 'Help & Support',
subtitle: 'Get help with',
searchPlaceholder: 'Search help articles...',
// ... rest of translations
};
</script>
<HelpPage
content={helpContent}
appName="Chat"
appId="chat"
{translations}
/>
```
### Custom Section Layout
```svelte
<script lang="ts">
import { FAQSection, FeaturesOverview } from '@manacore/shared-help-ui';
let { content } = $props();
</script>
<div class="max-w-6xl mx-auto">
<section>
<h2>Frequently Asked Questions</h2>
<FAQSection items={content.faq} translations={translations} />
</section>
<section>
<h2>Features</h2>
<FeaturesOverview items={content.features} translations={translations} />
</section>
</div>
```
### With Search Integration
```svelte
<script lang="ts">
import { HelpPage } from '@manacore/shared-help-ui';
import type { SearchResult } from '@manacore/shared-help-types';
let searchResults = $state<SearchResult[]>([]);
function handleSearch(query: string, results: SearchResult[]) {
console.log(`Searched for: ${query}`);
searchResults = results;
// Analytics tracking, etc.
}
</script>
<HelpPage
content={helpContent}
appName="Chat"
appId="chat"
translations={translations}
onSearch={handleSearch}
/>
```
## Accessibility
All components follow accessibility best practices:
- Semantic HTML (`<section>`, `<article>`, `<nav>`)
- ARIA attributes for expandable content
- Keyboard navigation support
- Focus management
- Screen reader friendly
## How to Use
```
"Read packages/shared-help-ui/.agent/ and help me with..."
```

View file

@ -0,0 +1,16 @@
# Memory: @manacore/shared-help-ui
## Recent Changes
<!-- Track significant updates to Svelte components -->
## Known Issues
<!-- Document any UI or accessibility issues -->
## Decisions Made
<!-- Record important architectural decisions -->
## Performance Notes
<!-- Track component performance and optimization notes -->
## Integration Notes
<!-- Notes about integration with SvelteKit and Tailwind -->

View file

@ -0,0 +1,528 @@
# Shared i18n Agent
## Module Information
**Package**: `@manacore/shared-i18n`
**Version**: 1.0.0
**Type**: ESM TypeScript internationalization library
**Dependencies**: `svelte` 5.43.14 (peer)
## Identity
I am the Shared i18n Agent, responsible for providing internationalization support across all ManaCore applications. I manage language definitions, translation utilities, locale detection, and pre-built translation sets for common UI elements, authentication, and help content. I support 50+ languages with full metadata including RTL support and flag emojis.
## Expertise
- **Language Support**: 50+ languages with native names, English names, and flag emojis
- **Locale Detection**: Browser locale detection with fallback chains
- **Translation Management**: Pre-built translations for common, auth, and help content
- **Localization Utilities**: Number, date, and relative time formatting
- **String Interpolation**: Template variable replacement in translations
- **RTL Support**: Right-to-left language detection and handling
- **Svelte Integration**: LanguageSelector component for quick integration
## Code Structure
```
src/
├── index.ts # Main export barrel
├── languages.ts # Language definitions (50+ languages)
├── utils.ts # i18n utility functions
├── components/
│ ├── index.ts # Component exports
│ └── LanguageSelector.svelte # Language selection component
└── translations/
├── common/
│ ├── index.ts # Common translations (actions, labels, errors)
│ ├── en.json
│ └── de.json
├── auth/
│ ├── index.ts # Auth translations (login, register, forgot password)
│ ├── en.json
│ ├── de.json
│ ├── it.json
│ ├── fr.json
│ └── es.json
└── help/
├── index.ts # Help/support translations
├── en.json
├── de.json
├── it.json
├── fr.json
└── es.json
```
### Export Structure
The package provides targeted exports:
- `.` - All utilities and definitions
- `./languages` - Language definitions only
- `./utils` - Utility functions only
- `./translations/common` - Common translations only
## Key Patterns
### 1. Language Definition Pattern
Comprehensive metadata for 50+ languages:
```typescript
import { LANGUAGES, getLanguageInfo, isLanguageSupported } from '@manacore/shared-i18n';
// Language info
const german = LANGUAGES.de;
// {
// nativeName: 'Deutsch',
// englishName: 'German',
// emoji: '🇩🇪'
// }
// Check support
if (isLanguageSupported('de')) {
const info = getLanguageInfo('de');
console.log(info.emoji, info.nativeName); // "🇩🇪 Deutsch"
}
// Get display name
getLanguageDisplayName('de'); // "🇩🇪 Deutsch"
```
**Supported Languages:**
- European: en, de, fr, es, it, pt, nl, pl, ru, sv, da, fi, nb, el, hu, ro, bg, hr, sk, sl, sr, lt, lv, et, mt, ga, cs, uk, tr
- Asian: ja, ko, zh, vi, th, id, ms, tl
- South Asian: hi, bn, ur
- Middle Eastern: ar, fa, he
- African: af
- Regional variants: pt-BR, es-MX
### 2. Locale Detection Pattern
Automatic locale detection with fallback chain:
```typescript
import { detectBrowserLocale, getInitialLocale } from '@manacore/shared-i18n';
const supportedLocales = ['de', 'en', 'fr', 'it'] as const;
// Detect browser locale
const browserLocale = detectBrowserLocale(supportedLocales, 'en');
// Falls back through: navigator.language → navigator.languages → default
// Get initial locale (localStorage → browser → default)
const locale = getInitialLocale('app-locale', supportedLocales, 'en');
// Priority: localStorage > browser > default
// Store selection
storeLocale('app-locale', locale);
```
### 3. Translation Pattern
Pre-built translation sets for common use cases:
```typescript
import {
getCommonTranslations,
getAuthTranslations,
getHelpTranslations
} from '@manacore/shared-i18n';
// Common translations
const common = getCommonTranslations('de');
common.common.actions.save; // "Speichern"
common.common.labels.loading; // "Lädt..."
common.errors.network; // "Netzwerkfehler. Bitte überprüfen Sie Ihre Verbindung."
common.validation.required; // "Dieses Feld ist erforderlich"
// Auth translations
const auth = getAuthTranslations('de');
auth.login.title; // "Anmelden"
auth.login.emailPlaceholder; // "E-Mail-Adresse"
auth.register.createAccountButton; // "Konto erstellen"
// Help translations
const help = getHelpTranslations('de');
help.faq.title; // "Häufig gestellte Fragen"
```
### 4. Localization Utilities Pattern
Format numbers, dates, and times according to locale:
```typescript
import {
formatLocalizedNumber,
formatLocalizedDate,
formatRelativeTime,
interpolate
} from '@manacore/shared-i18n';
// Numbers
formatLocalizedNumber(1234.56, 'de');
// "1.234,56"
formatLocalizedNumber(1234.56, 'de', { style: 'currency', currency: 'EUR' });
// "1.234,56 €"
// Dates
formatLocalizedDate(new Date(), 'de', { dateStyle: 'long' });
// "15. März 2024"
// Relative time
formatRelativeTime(yesterday, 'de');
// "vor 1 Tag"
// String interpolation
interpolate('Hello {name}!', { name: 'World' });
// "Hello World!"
interpolate(t.validation.minLength, { min: 8 });
// "Must be at least 8 characters"
```
### 5. Language Groups Pattern
Pre-defined language groups for filtering:
```typescript
import { LOCALE_GROUPS, getLanguagesByGroup } from '@manacore/shared-i18n';
// EU official languages
const euLanguages = getLanguagesByGroup('eu');
// ['en', 'de', 'fr', 'es', 'it', 'pt', 'nl', ...]
// Major world languages
const majorLanguages = getLanguagesByGroup('major');
// ['en', 'de', 'fr', 'es', 'it', 'pt', 'ru', 'ja', 'ko', 'zh', 'ar']
// Nordic languages
const nordicLanguages = getLanguagesByGroup('nordic');
// ['sv', 'da', 'fi', 'nb']
// RTL languages
const rtlLanguages = getLanguagesByGroup('rtl');
// ['ar', 'fa', 'he', 'ur']
// Check if RTL
isRTL('ar'); // true
isRTL('en'); // false
```
### 6. Language Dropdown Pattern
Helper functions for building language selectors:
```typescript
import {
getLanguageDropdownItems,
getCurrentLanguageLabel
} from '@manacore/shared-i18n';
const supportedLocales = ['de', 'en', 'it', 'fr', 'es'] as const;
const currentLocale = $state('de');
// Get dropdown items (compatible with @manacore/shared-ui PillDropdown)
const items = getLanguageDropdownItems(
supportedLocales,
currentLocale,
(locale) => { currentLocale = locale; }
);
// [
// { id: 'de', label: '🇩🇪 Deutsch', onClick: fn, active: true },
// { id: 'en', label: '🇬🇧 English', onClick: fn, active: false },
// ...
// ]
// Get current label for trigger button
const currentLabel = getCurrentLanguageLabel(currentLocale);
// "🇩🇪 Deutsch"
```
## Integration Points
### With SvelteKit Applications
```svelte
<!-- +layout.svelte -->
<script lang="ts">
import { getInitialLocale, storeLocale, getCommonTranslations } from '@manacore/shared-i18n';
import { setContext } from 'svelte';
const supportedLocales = ['de', 'en', 'it', 'fr', 'es'] as const;
let locale = $state(getInitialLocale('app-locale', supportedLocales, 'de'));
let t = $derived(getCommonTranslations(locale));
function setLocale(newLocale: string) {
locale = newLocale;
storeLocale('app-locale', newLocale);
}
setContext('locale', () => locale);
setContext('t', () => t);
setContext('setLocale', setLocale);
</script>
<!-- Use translations -->
<button>{t.common.actions.save}</button>
```
### With Auth Components
```svelte
<!-- LoginForm.svelte -->
<script lang="ts">
import { getAuthTranslations } from '@manacore/shared-i18n';
let locale = $state('de');
let t = $derived(getAuthTranslations(locale).login);
</script>
<h1>{t.title}</h1>
<input type="email" placeholder={t.emailPlaceholder} />
<input type="password" placeholder={t.passwordPlaceholder} />
<button>{t.signInButton}</button>
```
### With Validation
```typescript
import { getCommonTranslations, interpolate } from '@manacore/shared-i18n';
const t = getCommonTranslations('de');
function validatePassword(password: string) {
if (password.length < 8) {
return interpolate(t.validation.minLength, { min: 8 });
// "Muss mindestens 8 Zeichen lang sein"
}
return null;
}
```
## Common Translation Keys
### Common Translations
```typescript
common: {
actions: { save, cancel, delete, edit, create, ... }
labels: { loading, saving, noResults, required, ... }
time: { now, today, yesterday, tomorrow, ... }
status: { active, inactive, pending, completed, ... }
}
errors: { generic, network, timeout, notFound, ... }
validation: {
required, email, minLength, maxLength, ...
password: { minLength, uppercase, lowercase, ... }
}
auth: { signIn, signOut, signUp, email, password, ... }
settings: { title, account, profile, language, theme, ... }
```
### Auth Translations
```typescript
login: { title, subtitle, emailPlaceholder, passwordPlaceholder, ... }
register: { title, emailPlaceholder, passwordPlaceholder, ... }
forgotPassword: { titleForm, description, emailPlaceholder, ... }
```
## How to Use
### Installation
This package is internal to the monorepo. Add to dependencies in `package.json`:
```json
{
"dependencies": {
"@manacore/shared-i18n": "workspace:*"
}
}
```
### Import Examples
```typescript
// Language definitions
import {
LANGUAGES,
getLanguageCodes,
getLanguageInfo,
isLanguageSupported,
isRTL,
getLanguageDisplayName,
LOCALE_GROUPS,
getLanguagesByGroup,
type LanguageCode,
type LanguageInfo
} from '@manacore/shared-i18n';
// Utilities
import {
detectBrowserLocale,
getStoredLocale,
storeLocale,
getInitialLocale,
normalizeLocale,
getBaseLanguage,
findBestMatch,
formatLocalizedNumber,
formatLocalizedDate,
formatRelativeTime,
interpolate,
getLanguageDropdownItems,
getCurrentLanguageLabel
} from '@manacore/shared-i18n';
// Translations
import {
getCommonTranslations,
getAuthTranslations,
getHelpTranslations,
commonTranslationsEn,
authTranslationsEn,
type CommonTranslations,
type AuthTranslations
} from '@manacore/shared-i18n';
// Component
import { LanguageSelector } from '@manacore/shared-i18n';
```
### Best Practices
#### 1. Single Source of Truth
Store locale in one place and derive translations:
```typescript
let locale = $state('de');
let t = $derived(getCommonTranslations(locale));
let auth = $derived(getAuthTranslations(locale));
```
#### 2. Context for Shared State
Use Svelte context to share locale across components:
```typescript
// Layout
setContext('locale', () => locale);
setContext('t', () => t);
// Child component
const locale = getContext<() => string>('locale')();
const t = getContext<() => CommonTranslations>('t')();
```
#### 3. Persist User Preference
Store user's language choice in localStorage:
```typescript
function changeLocale(newLocale: string) {
locale = newLocale;
storeLocale('app-locale', newLocale);
}
```
#### 4. Interpolate Dynamic Values
Use interpolate for messages with variables:
```typescript
const message = interpolate(t.validation.minLength, { min: 8 });
// "Must be at least 8 characters"
```
#### 5. Handle RTL Layouts
Check for RTL languages and adjust layout:
```svelte
<div class:rtl={isRTL(locale)}>
<!-- Content -->
</div>
<style>
.rtl {
direction: rtl;
}
</style>
```
### Common Use Cases
1. **Multi-Language App Setup**
```typescript
const supportedLocales = ['de', 'en', 'it', 'fr', 'es'] as const;
const locale = getInitialLocale('app-locale', supportedLocales, 'de');
const t = getCommonTranslations(locale);
```
2. **Language Switcher**
```svelte
<PillDropdown
trigger={getCurrentLanguageLabel(locale)}
items={getLanguageDropdownItems(supportedLocales, locale, setLocale)}
/>
```
3. **Form Validation**
```typescript
if (!email) return t.validation.required;
if (!isValidEmail(email)) return t.validation.email;
```
4. **Error Messages**
```typescript
if (error.code === 'NETWORK_ERROR') {
toast.error(t.errors.network);
}
```
5. **Localized Dates**
```typescript
formatLocalizedDate(createdAt, locale, { dateStyle: 'medium' });
```
## Language Selector Component
```svelte
<script lang="ts">
import { LanguageSelector } from '@manacore/shared-i18n';
let locale = $state('de');
</script>
<LanguageSelector
bind:locale
supportedLocales={['de', 'en', 'it', 'fr', 'es']}
storageKey="app-locale"
/>
```
## Locale Normalization
```typescript
normalizeLocale('en-us'); // 'en-US'
normalizeLocale('pt_BR'); // 'pt-BR'
normalizeLocale('de'); // 'de'
getBaseLanguage('pt-BR'); // 'pt'
getBaseLanguage('en-US'); // 'en'
findBestMatch('pt-BR', ['pt', 'en'], 'en'); // 'pt'
findBestMatch('zh-CN', ['en', 'de'], 'en'); // 'en'
```
## Plural Rules
```typescript
import { getPluralCategory } from '@manacore/shared-i18n';
getPluralCategory(0, 'en'); // 'other'
getPluralCategory(1, 'en'); // 'one'
getPluralCategory(2, 'en'); // 'other'
// Use with translations
const messages = {
one: '{count} message',
other: '{count} messages'
};
const category = getPluralCategory(count, locale);
const text = interpolate(messages[category], { count });
```
## Notes
- **50+ Languages**: Comprehensive language support with full metadata
- **RTL Support**: Built-in detection for Arabic, Hebrew, Persian, Urdu
- **Locale Fallbacks**: Intelligent fallback chain for unsupported locales
- **Type Safety**: Full TypeScript types for all translations
- **Tree-Shakeable**: Targeted exports for minimal bundle size
- **SSR Safe**: All utilities handle server-side rendering
- **Pre-Built Translations**: Common, auth, and help translations included
- **Extensible**: Easy to add new languages or translation sets
- **Svelte Component**: Ready-to-use LanguageSelector component
- **Context Integration**: Designed for Svelte context pattern

View file

@ -0,0 +1,16 @@
# Shared i18n Memory
## Recent Changes
<!-- Track recent modifications, bug fixes, and enhancements -->
## Known Issues
<!-- Document any known bugs or limitations -->
## Translation Coverage
<!-- Track which apps use which translations and supported languages -->
## Language Usage
<!-- Document which languages are most commonly used across apps -->
## Integration Notes
<!-- Notes about how this package integrates with other packages -->

View file

@ -0,0 +1,456 @@
# @manacore/shared-icons Agent
## Module Information
**Package**: `@manacore/shared-icons`
**Type**: Icon Library
**Framework**: Svelte 5 (Phosphor Icons)
**Purpose**: Centralized icon system for all ManaCore SvelteKit web applications
## Identity
I am the Shared Icons Agent. I provide a unified icon library based on Phosphor Icons for all SvelteKit web applications in the ManaCore monorepo. I ensure consistent iconography across all apps by re-exporting phosphor-svelte components with a single import point.
## Expertise
- **Phosphor Icons**: Complete icon set from phosphor-svelte (https://phosphoricons.com)
- **Icon Variants**: Support for thin, light, regular, bold, fill, and duotone weights
- **Svelte 5 Components**: All icons are Svelte 5 components
- **Consistent API**: Unified props interface (size, weight, color, class)
- **Tree-Shakeable**: Only import icons you use
- **TypeScript**: Full TypeScript support for all icons
## Code Structure
```
src/
└── index.ts # Re-exports all phosphor-svelte icons
```
### Main Export File
```typescript
/**
* @manacore/shared-icons
*
* Phosphor Icons for all Manacore SvelteKit web apps
* https://phosphoricons.com
*
* Usage:
* import { House, User, Gear, Plus } from '@manacore/shared-icons';
*
* <House size={24} weight="bold" />
* <User size={20} weight="regular" class="text-blue-500" />
*
* Available weights: thin, light, regular, bold, fill, duotone
*/
export * from 'phosphor-svelte';
```
## Key Patterns
### Icon Component Props
All Phosphor icons accept these props:
```typescript
interface IconProps {
size?: number | string; // Size in pixels (default: 24)
weight?: IconWeight; // Icon weight/style
color?: string; // CSS color value
mirrored?: boolean; // Mirror the icon horizontally
class?: string; // Additional CSS classes
}
type IconWeight = 'thin' | 'light' | 'regular' | 'bold' | 'fill' | 'duotone';
```
### Basic Usage
```svelte
<script lang="ts">
import { House, User, Gear } from '@manacore/shared-icons';
</script>
<!-- Default (24px, regular weight) -->
<House />
<!-- Custom size -->
<User size={32} />
<!-- Custom weight -->
<Gear weight="bold" />
<!-- With color and classes -->
<House size={20} weight="fill" class="text-primary hover:text-primary/80" />
```
### Weight Variations
```svelte
<script lang="ts">
import { Heart } from '@manacore/shared-icons';
</script>
<Heart weight="thin" /> <!-- Thinnest stroke -->
<Heart weight="light" /> <!-- Light stroke -->
<Heart weight="regular" /> <!-- Default stroke -->
<Heart weight="bold" /> <!-- Thick stroke -->
<Heart weight="fill" /> <!-- Filled solid -->
<Heart weight="duotone" /> <!-- Two-tone style -->
```
### Dynamic Icons
```svelte
<script lang="ts">
import * as Icons from '@manacore/shared-icons';
interface Props {
iconName: string;
}
let { iconName }: Props = $props();
// Get icon component dynamically
const IconComponent = $derived(Icons[iconName as keyof typeof Icons]);
</script>
{#if IconComponent}
<svelte:component this={IconComponent} size={24} />
{/if}
```
### Animated Icons
```svelte
<script lang="ts">
import { Spinner, CircleNotch } from '@manacore/shared-icons';
</script>
<!-- Loading spinner -->
<Spinner size={24} class="animate-spin" />
<!-- Alternative spinner -->
<CircleNotch size={24} weight="bold" class="animate-spin" />
```
## Integration Points
### Dependencies
- **phosphor-svelte**: ^3.0.1 (Phosphor Icons for Svelte)
### Peer Dependencies
- **svelte**: ^5.0.0
### Used By
- **@manacore/shared-ui**: UI component library
- All SvelteKit web applications in the monorepo
- Navigation components
- Button components
- Settings interfaces
- Any component requiring icons
### Common Integration with shared-ui
```svelte
<script lang="ts">
import { Button } from '@manacore/shared-ui';
import { Plus, Trash } from '@manacore/shared-icons';
</script>
<Button>
<Plus size={16} weight="bold" />
Create New
</Button>
<Button variant="danger">
<Trash size={16} />
Delete
</Button>
```
## How to Use
### Installation
This package is internal to the monorepo and uses workspace protocol:
```json
{
"dependencies": {
"@manacore/shared-icons": "workspace:*"
}
}
```
### Common Icons Reference
#### Navigation & UI
```svelte
import {
House, // Home
List, // Menu
X, // Close
CaretLeft, // Back
CaretRight, // Forward
CaretDown, // Dropdown
DotsThree, // More options
MagnifyingGlass, // Search
Plus, // Add
Minus, // Remove
Check, // Confirm
} from '@manacore/shared-icons';
```
#### Actions
```svelte
import {
PencilSimple, // Edit
Trash, // Delete
Copy, // Copy
Download, // Download
Upload, // Upload
Share, // Share
Heart, // Favorite
Star, // Bookmark
Archive, // Archive
Eye, // View
EyeSlash, // Hide
} from '@manacore/shared-icons';
```
#### Status & Feedback
```svelte
import {
CheckCircle, // Success
XCircle, // Error
Warning, // Warning
Info, // Information
Question, // Help
Spinner, // Loading
CircleNotch, // Alternative loading
} from '@manacore/shared-icons';
```
#### User & Account
```svelte
import {
User, // User profile
UserCircle, // User avatar
Users, // Multiple users
UserPlus, // Add user
SignOut, // Logout
SignIn, // Login
Key, // Password
Lock, // Security
} from '@manacore/shared-icons';
```
#### Settings & Configuration
```svelte
import {
Gear, // Settings
Sliders, // Adjust/preferences
Palette, // Theme
Bell, // Notifications
Moon, // Dark mode
Sun, // Light mode
GlobeHemisphereWest, // Language/region
} from '@manacore/shared-icons';
```
#### Communication
```svelte
import {
ChatCircle, // Chat
ChatCircleText, // Message
Envelope, // Email
EnvelopeOpen, // Read email
Phone, // Call
PaperPlane, // Send
} from '@manacore/shared-icons';
```
#### Files & Documents
```svelte
import {
File, // File
FileText, // Document
Folder, // Folder
FolderOpen, // Open folder
Image, // Image file
FilePdf, // PDF
FileCode, // Code file
Note, // Note
Notebook, // Notebook
} from '@manacore/shared-icons';
```
#### Calendar & Time
```svelte
import {
Calendar, // Calendar
CalendarBlank, // Empty calendar
Clock, // Time
ClockCountdown, // Timer
Timer, // Stopwatch
CalendarPlus, // Add event
} from '@manacore/shared-icons';
```
#### Data & Charts
```svelte
import {
ChartBar, // Bar chart
ChartLine, // Line chart
ChartPie, // Pie chart
TrendUp, // Trending up
TrendDown, // Trending down
Activity, // Activity/stats
} from '@manacore/shared-icons';
```
### Complete Usage Example
```svelte
<script lang="ts">
import { Button, Input, Card } from '@manacore/shared-ui';
import {
MagnifyingGlass,
Plus,
Trash,
PencilSimple,
Check,
X,
} from '@manacore/shared-icons';
let searchQuery = $state('');
let editMode = $state(false);
</script>
<Card>
<div class="flex items-center gap-2 mb-4">
<MagnifyingGlass size={20} class="text-theme/60" />
<Input bind:value={searchQuery} placeholder="Search..." />
</div>
<div class="flex gap-2">
<Button variant="primary">
<Plus size={16} weight="bold" />
Create New
</Button>
{#if editMode}
<Button variant="success">
<Check size={16} weight="bold" />
Save
</Button>
<Button variant="secondary" onclick={() => editMode = false}>
<X size={16} />
Cancel
</Button>
{:else}
<Button variant="secondary" onclick={() => editMode = true}>
<PencilSimple size={16} />
Edit
</Button>
<Button variant="danger">
<Trash size={16} />
Delete
</Button>
{/if}
</div>
</Card>
```
### Responsive Icon Sizes
```svelte
<script lang="ts">
import { House } from '@manacore/shared-icons';
</script>
<!-- Mobile: 16px, Desktop: 24px -->
<House size={16} class="sm:hidden" />
<House size={24} class="hidden sm:block" />
<!-- Or use CSS -->
<House class="w-4 h-4 sm:w-6 sm:h-6" />
```
### Themed Icons
```svelte
<script lang="ts">
import { Sun, Moon } from '@manacore/shared-icons';
let isDark = $state(false);
</script>
<!-- Toggle icon based on theme -->
{#if isDark}
<Moon size={20} weight="fill" class="text-primary" />
{:else}
<Sun size={20} weight="fill" class="text-yellow-500" />
{/if}
```
## Best Practices
1. **Consistent Sizing**: Use standard sizes (16, 20, 24, 32) for consistency
2. **Weight Selection**: Use `regular` for most cases, `bold` for emphasis, `fill` for active states
3. **Color Inheritance**: Let icons inherit text color when possible using `class="text-*"`
4. **Accessibility**: Pair icons with text labels or aria-labels
5. **Tree Shaking**: Import only the icons you need
6. **Named Imports**: Always use named imports, never `import * as Icons` in production
7. **Animation**: Use Tailwind's `animate-spin` for loading spinners
8. **Responsive**: Adjust icon size for mobile vs desktop
9. **Semantic Usage**: Choose icons that clearly represent their function
10. **Weight Consistency**: Use the same weight across similar UI elements
## Icon Categories
### Core UI (Most Used)
- House, List, X, Plus, Minus, Check
- MagnifyingGlass, DotsThree, CaretDown
### Actions
- PencilSimple, Trash, Copy, Share, Download, Upload
- Heart, Star, Archive, Eye, EyeSlash
### Status
- CheckCircle, XCircle, Warning, Info, Question
- Spinner, CircleNotch
### User
- User, UserCircle, Users, UserPlus
- SignIn, SignOut, Key, Lock
### Settings
- Gear, Sliders, Palette, Bell, Moon, Sun
### Content
- File, Folder, Image, Note, Calendar, Clock
### Charts
- ChartBar, ChartLine, ChartPie, TrendUp, Activity
## Resources
- **Phosphor Icons**: https://phosphoricons.com
- **Icon Search**: Browse all available icons at phosphoricons.com
- **GitHub**: https://github.com/phosphor-icons/svelte

View file

@ -0,0 +1,33 @@
# @manacore/shared-icons Memory
## Changelog
### Recent Changes
- No changes recorded yet
## Common Issues & Solutions
### Issue Template
**Problem**:
**Solution**:
**Date**:
## Patterns & Learnings
### Icon Usage Patterns
- No patterns documented yet
## Technical Decisions
### Decision Template
**Decision**:
**Reason**:
**Date**:
**Impact**:
## Notes
- This memory file is maintained by agents working on @manacore/shared-icons
- Add significant changes, decisions, and learnings here
- Document commonly used icon combinations
- Note any icon deprecations or replacements

View file

@ -0,0 +1,605 @@
# @manacore/shared-landing-ui Agent
## Module Information
**Package**: `@manacore/shared-landing-ui`
**Type**: Landing Page Component Library
**Framework**: Astro 5
**Purpose**: Shared Astro components for marketing landing pages across ManaCore apps
## Identity
I am the Shared Landing UI Agent. I provide a comprehensive set of Astro components for building beautiful, consistent landing pages across the ManaCore ecosystem. I include pre-built sections (Hero, Features, Pricing, FAQ, etc.), atomic components (Button, Card, Badge), and multi-brand theming support.
## Expertise
- **Astro Components**: Static site generation with Astro 5
- **Multi-Brand Theming**: CSS custom property-based themes (Manacore, Memoro, Maerchenzauber, Manadeck)
- **Landing Page Sections**: Pre-built Hero, Features, Pricing, Testimonials, CTA, FAQ sections
- **Responsive Design**: Mobile-first responsive components
- **SEO Optimized**: Semantic HTML for better SEO
- **Accessibility**: ARIA labels, keyboard navigation, semantic structure
- **Performance**: Static generation, optimized CSS, minimal JavaScript
## Code Structure
```
src/
├── atoms/ # Basic building blocks
│ ├── Button.astro # Call-to-action button
│ ├── Badge.astro # Label/tag badge
│ ├── Card.astro # Container card
│ ├── Container.astro # Max-width container
│ └── SectionHeader.astro # Section title + subtitle
├── sections/ # Complete landing sections
│ ├── HeroSection.astro # Hero/banner section
│ ├── FeatureSection.astro # Features grid
│ ├── PricingSection.astro # Pricing plans
│ ├── TestimonialSection.astro # User testimonials
│ ├── CTASection.astro # Call-to-action section
│ ├── FAQSection.astro # FAQ accordion
│ └── StepsSection.astro # Step-by-step guide
├── layouts/
│ └── Footer.astro # Page footer
├── themes/ # Brand theme CSS
│ ├── index.css # Base theme variables
│ ├── manacore.css # Manacore brand
│ ├── memoro.css # Memoro brand
│ ├── maerchenzauber.css # Maerchenzauber brand
│ └── manadeck.css # Manadeck brand
├── utils/
│ └── index.ts # Theme utilities
└── index.ts # Main exports
```
## Key Patterns
### Theme System
All components use CSS custom properties for theming:
```css
/* Required CSS Custom Properties */
--color-primary /* Brand primary color */
--color-primary-hover /* Primary hover state */
--color-secondary /* Secondary color */
--color-background /* Page background */
--color-background-card /* Card background */
--color-background-card-hover /* Card hover state */
--color-text-primary /* Primary text */
--color-text-secondary /* Secondary/muted text */
--color-border /* Border color */
--font-family-sans /* Sans-serif font */
--font-family-heading /* Heading font */
```
### Atom Components
#### Button
```astro
---
import Button from '@manacore/shared-landing-ui/atoms/Button.astro';
---
<!-- Primary CTA -->
<Button href="/signup" variant="primary" size="lg">
Get Started
</Button>
<!-- Secondary action -->
<Button href="/learn-more" variant="secondary">
Learn More
</Button>
<!-- Outline style -->
<Button href="/docs" variant="outline" size="sm">
Documentation
</Button>
<!-- Ghost style (transparent) -->
<Button href="/about" variant="ghost">
About Us
</Button>
<!-- Full width button -->
<Button href="/signup" variant="primary" fullWidth>
Sign Up Now
</Button>
```
#### Card
```astro
---
import Card from '@manacore/shared-landing-ui/atoms/Card.astro';
---
<Card>
<h3>Feature Title</h3>
<p>Feature description goes here.</p>
</Card>
<!-- With hover effect -->
<Card hover>
<h3>Interactive Card</h3>
</Card>
<!-- Custom padding -->
<Card class="p-8">
<h3>Custom Padding</h3>
</Card>
```
#### Badge
```astro
---
import Badge from '@manacore/shared-landing-ui/atoms/Badge.astro';
---
<Badge variant="primary">New</Badge>
<Badge variant="success">Popular</Badge>
<Badge variant="warning">Limited</Badge>
```
#### Container
```astro
---
import Container from '@manacore/shared-landing-ui/atoms/Container.astro';
---
<Container>
<h1>Page content within max-width container</h1>
</Container>
<!-- Different max widths -->
<Container size="sm">Small container</Container>
<Container size="lg">Large container</Container>
```
#### SectionHeader
```astro
---
import SectionHeader from '@manacore/shared-landing-ui/atoms/SectionHeader.astro';
---
<SectionHeader
title="Our Features"
subtitle="Everything you need to succeed"
/>
<!-- With badge -->
<SectionHeader
badge="New"
title="Latest Updates"
subtitle="Check out what's new"
/>
```
### Section Components
#### HeroSection
```astro
---
import HeroSection from '@manacore/shared-landing-ui/sections/HeroSection.astro';
import Button from '@manacore/shared-landing-ui/atoms/Button.astro';
---
<HeroSection
title="Welcome to Our Product"
subtitle="The best solution for your needs"
badge="New Release"
>
<div slot="cta">
<Button href="/signup" variant="primary" size="lg">Get Started</Button>
<Button href="/demo" variant="secondary" size="lg">Watch Demo</Button>
</div>
<div slot="image">
<img src="/hero-image.png" alt="Product screenshot" />
</div>
</HeroSection>
```
#### FeatureSection
```astro
---
import FeatureSection from '@manacore/shared-landing-ui/sections/FeatureSection.astro';
---
<FeatureSection
title="Amazing Features"
subtitle="Everything you need in one place"
features={[
{
icon: 'rocket',
title: 'Fast Performance',
description: 'Lightning-fast load times',
},
{
icon: 'shield',
title: 'Secure',
description: 'Enterprise-grade security',
},
{
icon: 'users',
title: 'Collaborative',
description: 'Work together seamlessly',
},
]}
/>
```
#### PricingSection
```astro
---
import PricingSection from '@manacore/shared-landing-ui/sections/PricingSection.astro';
---
<PricingSection
title="Simple Pricing"
subtitle="Choose the plan that's right for you"
plans={[
{
name: 'Free',
price: '$0',
period: 'forever',
features: ['10 projects', 'Basic support', '1GB storage'],
cta: { text: 'Get Started', href: '/signup' },
},
{
name: 'Pro',
price: '$19',
period: 'per month',
features: ['Unlimited projects', 'Priority support', '100GB storage'],
cta: { text: 'Start Trial', href: '/trial' },
highlighted: true,
},
]}
/>
```
#### TestimonialSection
```astro
---
import TestimonialSection from '@manacore/shared-landing-ui/sections/TestimonialSection.astro';
---
<TestimonialSection
title="What Our Users Say"
testimonials={[
{
quote: 'This product changed how we work!',
author: 'Jane Doe',
role: 'CEO, Company Inc',
avatar: '/avatars/jane.jpg',
},
{
quote: 'Amazing support and features.',
author: 'John Smith',
role: 'Developer',
},
]}
/>
```
#### CTASection
```astro
---
import CTASection from '@manacore/shared-landing-ui/sections/CTASection.astro';
import Button from '@manacore/shared-landing-ui/atoms/Button.astro';
---
<CTASection
title="Ready to Get Started?"
subtitle="Join thousands of happy users today"
>
<div slot="cta">
<Button href="/signup" variant="primary" size="lg">
Start Free Trial
</Button>
</div>
</CTASection>
```
#### FAQSection
```astro
---
import FAQSection from '@manacore/shared-landing-ui/sections/FAQSection.astro';
---
<FAQSection
title="Frequently Asked Questions"
faqs={[
{
question: 'How does it work?',
answer: 'It works by doing amazing things automatically.',
},
{
question: 'Is it free?',
answer: 'Yes, we offer a free plan with basic features.',
},
]}
/>
```
#### StepsSection
```astro
---
import StepsSection from '@manacore/shared-landing-ui/sections/StepsSection.astro';
---
<StepsSection
title="How It Works"
subtitle="Get started in three simple steps"
steps={[
{
number: 1,
title: 'Sign Up',
description: 'Create your free account',
},
{
number: 2,
title: 'Configure',
description: 'Set up your preferences',
},
{
number: 3,
title: 'Launch',
description: 'Start using the platform',
},
]}
/>
```
### Complete Landing Page Example
```astro
---
import HeroSection from '@manacore/shared-landing-ui/sections/HeroSection.astro';
import FeatureSection from '@manacore/shared-landing-ui/sections/FeatureSection.astro';
import PricingSection from '@manacore/shared-landing-ui/sections/PricingSection.astro';
import CTASection from '@manacore/shared-landing-ui/sections/CTASection.astro';
import Footer from '@manacore/shared-landing-ui/layouts/Footer.astro';
import Button from '@manacore/shared-landing-ui/atoms/Button.astro';
// Import theme CSS
import '@manacore/shared-landing-ui/themes/manacore';
---
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>My Product</title>
</head>
<body>
<HeroSection
title="Transform Your Workflow"
subtitle="The all-in-one solution for modern teams"
badge="New"
>
<div slot="cta">
<Button href="/signup" variant="primary" size="lg">Get Started Free</Button>
<Button href="/demo" variant="secondary" size="lg">Watch Demo</Button>
</div>
</HeroSection>
<FeatureSection
title="Powerful Features"
subtitle="Everything you need to succeed"
features={[
{ icon: 'rocket', title: 'Fast', description: 'Lightning fast' },
{ icon: 'shield', title: 'Secure', description: 'Bank-level security' },
{ icon: 'users', title: 'Collaborative', description: 'Team-friendly' },
]}
/>
<PricingSection
title="Simple Pricing"
plans={[
{
name: 'Free',
price: '$0',
features: ['10 projects', 'Basic support'],
cta: { text: 'Get Started', href: '/signup' },
},
{
name: 'Pro',
price: '$19',
period: 'per month',
features: ['Unlimited projects', 'Priority support'],
cta: { text: 'Start Trial', href: '/trial' },
highlighted: true,
},
]}
/>
<CTASection
title="Ready to Get Started?"
subtitle="Join thousands of happy users"
>
<div slot="cta">
<Button href="/signup" variant="primary" size="lg">
Start Free Trial
</Button>
</div>
</CTASection>
<Footer
companyName="My Company"
links={[
{ text: 'About', href: '/about' },
{ text: 'Contact', href: '/contact' },
{ text: 'Privacy', href: '/privacy' },
]}
/>
</body>
</html>
```
## Integration Points
### Dependencies
- **astro**: ^5.16.0 (Static Site Generator)
- **astro-icon**: ^1.0.0 (Icon integration)
### Peer Dependencies
- **astro**: >=5.0.0
- **astro-icon**: >=1.0.0
### Theme CSS Imports
```astro
---
// Import base theme variables (required)
import '@manacore/shared-landing-ui/themes';
// Import brand-specific theme
import '@manacore/shared-landing-ui/themes/manacore';
// or
import '@manacore/shared-landing-ui/themes/memoro';
// or
import '@manacore/shared-landing-ui/themes/maerchenzauber';
// or
import '@manacore/shared-landing-ui/themes/manadeck';
---
```
### Used By
- Landing pages for all ManaCore apps
- Marketing websites
- Product announcement pages
- Documentation landing pages
## How to Use
### Installation
This package is internal to the monorepo:
```json
{
"dependencies": {
"@manacore/shared-landing-ui": "workspace:*"
}
}
```
### Setup in Astro Project
1. **Import theme CSS in your layout**:
```astro
---
// src/layouts/Layout.astro
import '@manacore/shared-landing-ui/themes/manacore';
---
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>{Astro.props.title}</title>
</head>
<body>
<slot />
</body>
</html>
```
2. **Use components in pages**:
```astro
---
// src/pages/index.astro
import Layout from '../layouts/Layout.astro';
import HeroSection from '@manacore/shared-landing-ui/sections/HeroSection.astro';
import Button from '@manacore/shared-landing-ui/atoms/Button.astro';
---
<Layout title="Home">
<HeroSection title="Welcome" subtitle="Get started today">
<div slot="cta">
<Button href="/signup" variant="primary">Sign Up</Button>
</div>
</HeroSection>
</Layout>
```
### Creating a Custom Theme
Create a new CSS file with custom properties:
```css
/* themes/custom.css */
:root {
--color-primary: #6366f1;
--color-primary-hover: #4f46e5;
--color-secondary: #ec4899;
--color-background: #ffffff;
--color-background-card: #f9fafb;
--color-background-card-hover: #f3f4f6;
--color-text-primary: #111827;
--color-text-secondary: #6b7280;
--color-border: #e5e7eb;
--font-family-sans: 'Inter', sans-serif;
--font-family-heading: 'Inter', sans-serif;
}
```
## Best Practices
1. **Always Import Theme**: Import a theme CSS file in your layout
2. **Use Slots**: Leverage Astro slots for flexible content insertion
3. **Semantic HTML**: Components use semantic HTML for SEO
4. **Responsive First**: All components are mobile-responsive by default
5. **Consistent Spacing**: Use Container components for consistent page width
6. **Accessibility**: Add alt text to images, aria-labels where needed
7. **Performance**: Components are static by default, minimal JavaScript
8. **Type Safety**: Use TypeScript for component props when possible
9. **Brand Consistency**: Use pre-built themes or create consistent custom themes
10. **Section Order**: Follow common landing page pattern (Hero → Features → Pricing → CTA)
## Component Categories
### Atoms
- Button, Badge, Card, Container, SectionHeader
### Sections
- HeroSection, FeatureSection, PricingSection, TestimonialSection, CTASection, FAQSection, StepsSection
### Layouts
- Footer
### Themes
- Manacore, Memoro, Maerchenzauber, Manadeck, Custom
## Available Themes
### Manacore Theme
Default ManaCore brand colors and typography
### Memoro Theme
Memoro app brand styling
### Maerchenzauber Theme
Maerchenzauber app brand styling
### Manadeck Theme
Manadeck app brand styling

View file

@ -0,0 +1,36 @@
# @manacore/shared-landing-ui Memory
## Changelog
### Recent Changes
- No changes recorded yet
## Common Issues & Solutions
### Issue Template
**Problem**:
**Solution**:
**Date**:
## Patterns & Learnings
### Landing Page Patterns
- No patterns documented yet
### Theme Customization
- No customizations documented yet
## Technical Decisions
### Decision Template
**Decision**:
**Reason**:
**Date**:
**Impact**:
## Notes
- This memory file is maintained by agents working on @manacore/shared-landing-ui
- Add significant changes, decisions, and learnings here
- Document new theme additions
- Note common landing page layouts

View file

@ -0,0 +1,468 @@
# Agent: @manacore/shared-nestjs-auth
## Module Information
**Package:** `@manacore/shared-nestjs-auth`
**Type:** Shared Package (NestJS Auth Utilities)
**Location:** `/packages/shared-nestjs-auth`
**Dependencies:** `@manacore/better-auth-types`, `@nestjs/common@^10-11`, `@nestjs/config@^3-4`
## Identity
I am the NestJS authentication specialist for the ManaCore monorepo. I provide guards, decorators, and utilities for protecting NestJS backend routes with JWT authentication. I validate tokens via the central Mana Core Auth service and extract user information for use in controllers and services.
## Expertise
### Core Capabilities
1. **JWT Authentication Guard**
- `JwtAuthGuard` - Validates JWT tokens via Mana Core Auth
- Protects routes and controller methods
- Development mode bypass for local testing
- Automatic user data extraction
2. **Current User Decorator**
- `@CurrentUser()` - Extract authenticated user from request
- Type-safe with `CurrentUserData` interface
- Works seamlessly with `JwtAuthGuard`
3. **Token Validation**
- Validates tokens against Mana Core Auth service
- Extracts user ID, email, role, session ID
- Handles token expiration and invalid tokens
- Returns standardized user data structure
4. **Development Helpers**
- Bypass authentication in development mode
- Configurable dev user ID for testing
- No token required when bypassed
### Technical Patterns
- **Guard-Based Protection**: Use NestJS guards for route protection
- **Decorator-Based Access**: Extract user data with parameter decorators
- **Centralized Validation**: All validation via Mana Core Auth service
- **Type-Safe**: Full TypeScript support with strict types
- **ConfigService Integration**: Environment-based configuration
## Code Structure
```
src/
├── index.ts # Public exports
├── types/
│ └── index.ts # Type definitions and re-exports
├── guards/
│ └── jwt-auth.guard.ts # JWT validation guard
└── decorators/
└── current-user.decorator.ts # User extraction decorator
```
## Key Files
### `src/guards/jwt-auth.guard.ts`
Main authentication guard:
- Implements `CanActivate` interface
- Extracts Bearer token from Authorization header
- Validates token with Mana Core Auth service (`POST /api/v1/auth/validate`)
- Attaches user data to request object
- Supports development bypass (`DEV_BYPASS_AUTH=true`)
- Configurable dev user ID (`DEV_USER_ID`)
- Throws `UnauthorizedException` for invalid/missing tokens
### `src/decorators/current-user.decorator.ts`
Parameter decorator for user extraction:
- Uses `createParamDecorator` from NestJS
- Extracts `request.user` from execution context
- Returns `CurrentUserData` type
- Must be used with `JwtAuthGuard`
### `src/types/index.ts`
Type definitions:
- Re-exports from `@manacore/better-auth-types`:
- `CurrentUserData` - User data interface (userId, email, role, sessionId)
- `JWTPayload` - Raw JWT payload structure
- `TokenValidationResponse` - Auth service response
- `UserRole` - User role enum
- `OrganizationRole` - Organization role enum
- `AuthModuleConfig` - Configuration interface for auth setup
## Integration Points
### Consumed By
- **Backend Apps** (NestJS): Import guard and decorator in controllers
- All NestJS services in monorepo
- `apps/*/backend` - All app backends
### Dependencies
- `@manacore/better-auth-types` - Centralized auth type definitions
- `@nestjs/common` - NestJS core (guards, decorators, exceptions)
- `@nestjs/config` - Environment variable management
### Related Packages
- `@manacore/shared-auth-stores` - Frontend auth state management
- `@manacore/shared-auth-ui` - Frontend auth UI components
- `services/mana-core-auth` - Central auth service (validates tokens)
### Integration with Mana Core Auth
This package communicates with the central auth service:
- **Endpoint**: `POST /api/v1/auth/validate`
- **Payload**: `{ token: string }`
- **Response**: `TokenValidationResponse` with user data
- **URL**: Configured via `MANA_CORE_AUTH_URL` env var (default: `http://localhost:3001`)
## Key Patterns
### 1. Basic Route Protection
```typescript
import { Controller, Get, UseGuards } from '@nestjs/common';
import { JwtAuthGuard, CurrentUser, CurrentUserData } from '@manacore/shared-nestjs-auth';
@Controller('api/profile')
@UseGuards(JwtAuthGuard)
export class ProfileController {
@Get()
getProfile(@CurrentUser() user: CurrentUserData) {
return {
userId: user.userId,
email: user.email,
role: user.role,
};
}
}
```
### 2. Protecting Specific Endpoints
```typescript
import { Controller, Get, Post, UseGuards } from '@nestjs/common';
import { JwtAuthGuard, CurrentUser, CurrentUserData } from '@manacore/shared-nestjs-auth';
@Controller('api/posts')
export class PostsController {
// Public endpoint - no guard
@Get()
getAllPosts() {
return this.postsService.findAll();
}
// Protected endpoint - requires authentication
@Post()
@UseGuards(JwtAuthGuard)
createPost(
@Body() createPostDto: CreatePostDto,
@CurrentUser() user: CurrentUserData
) {
return this.postsService.create(createPostDto, user.userId);
}
}
```
### 3. Using User Data in Services
```typescript
import { Injectable } from '@nestjs/common';
import { CurrentUserData } from '@manacore/shared-nestjs-auth';
@Injectable()
export class PostsService {
async create(createPostDto: CreatePostDto, userId: string) {
return this.db.post.create({
data: {
...createPostDto,
authorId: userId,
},
});
}
async update(postId: string, updatePostDto: UpdatePostDto, user: CurrentUserData) {
// Check ownership
const post = await this.db.post.findUnique({ where: { id: postId } });
if (post.authorId !== user.userId) {
throw new ForbiddenException('You can only edit your own posts');
}
return this.db.post.update({
where: { id: postId },
data: updatePostDto,
});
}
}
```
### 4. Custom Role-Based Guard
```typescript
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { JwtAuthGuard, CurrentUserData } from '@manacore/shared-nestjs-auth';
import { Reflector } from '@nestjs/core';
@Injectable()
export class RolesGuard implements CanActivate {
constructor(private reflector: Reflector) {}
canActivate(context: ExecutionContext): boolean {
const requiredRoles = this.reflector.get<string[]>('roles', context.getHandler());
if (!requiredRoles) return true;
const request = context.switchToHttp().getRequest();
const user: CurrentUserData = request.user;
return requiredRoles.includes(user.role);
}
}
// Usage:
@Controller('api/admin')
@UseGuards(JwtAuthGuard, RolesGuard)
@Roles('admin')
export class AdminController {
// Only accessible to admin users
}
```
### 5. Development Mode Bypass
```bash
# .env.development
NODE_ENV=development
DEV_BYPASS_AUTH=true
DEV_USER_ID=550e8400-e29b-41d4-a716-446655440000
MANA_CORE_AUTH_URL=http://localhost:3001
```
```typescript
// Guard automatically bypasses auth and returns dev user
@Controller('api/test')
@UseGuards(JwtAuthGuard)
export class TestController {
@Get()
test(@CurrentUser() user: CurrentUserData) {
// In dev mode with bypass, user will be the dev user
return user; // { userId: '550e8400...', email: 'dev@example.com', ... }
}
}
```
### 6. Global Guard Setup
```typescript
// In your main.ts or app.module.ts
import { APP_GUARD } from '@nestjs/core';
import { JwtAuthGuard } from '@manacore/shared-nestjs-auth';
@Module({
providers: [
{
provide: APP_GUARD,
useClass: JwtAuthGuard,
},
],
})
export class AppModule {}
// Now all routes are protected by default
// Use @Public() decorator (create your own) to mark public routes
```
### 7. Error Handling
```typescript
import { Controller, Get, UseGuards, HttpException } from '@nestjs/common';
import { JwtAuthGuard, CurrentUser, CurrentUserData } from '@manacore/shared-nestjs-auth';
@Controller('api/user')
@UseGuards(JwtAuthGuard)
export class UserController {
@Get('settings')
async getSettings(@CurrentUser() user: CurrentUserData) {
try {
return await this.userService.getSettings(user.userId);
} catch (error) {
throw new HttpException('Failed to load settings', 500);
}
}
}
// Guard automatically throws UnauthorizedException for:
// - Missing token
// - Invalid token
// - Expired token
// - Auth service unavailable
```
### 8. Multi-Tenant Pattern
```typescript
@Controller('api/organizations/:orgId')
@UseGuards(JwtAuthGuard)
export class OrganizationController {
@Get('projects')
async getProjects(
@Param('orgId') orgId: string,
@CurrentUser() user: CurrentUserData
) {
// Check if user has access to this organization
const hasAccess = await this.orgService.userHasAccess(orgId, user.userId);
if (!hasAccess) {
throw new ForbiddenException('Access denied');
}
return this.projectService.findByOrganization(orgId);
}
}
```
## How to Use
### For Backend Developers
1. **Install Package**:
```bash
# Already available in monorepo
# Just import from @manacore/shared-nestjs-auth
```
2. **Configure Environment Variables**:
```bash
# .env or .env.development
MANA_CORE_AUTH_URL=http://localhost:3001
# Optional: for development
NODE_ENV=development
DEV_BYPASS_AUTH=true
DEV_USER_ID=your-test-user-id
```
3. **Import in Controllers**:
```typescript
import { JwtAuthGuard, CurrentUser, CurrentUserData } from '@manacore/shared-nestjs-auth';
```
4. **Protect Routes**:
```typescript
@UseGuards(JwtAuthGuard)
```
5. **Access User Data**:
```typescript
@CurrentUser() user: CurrentUserData
```
6. **Pass to Services**:
```typescript
this.myService.doSomething(user.userId);
```
### Frontend Integration
Frontend apps must send tokens in Authorization header:
```typescript
// In your frontend API client
const response = await fetch('/api/protected', {
headers: {
'Authorization': `Bearer ${token}`,
},
});
```
### Token Flow
1. User signs in via frontend (using `@manacore/shared-auth-ui` or custom UI)
2. Frontend receives JWT token from Mana Core Auth
3. Frontend stores token (localStorage, cookie, etc.)
4. Frontend includes token in Authorization header for API requests
5. Backend `JwtAuthGuard` extracts and validates token
6. Guard calls Mana Core Auth to validate token
7. If valid, user data attached to request
8. Controller receives user data via `@CurrentUser()` decorator
### Environment Setup
Each backend needs these environment variables:
```bash
# Required
MANA_CORE_AUTH_URL=http://localhost:3001
# Optional - for development
DEV_BYPASS_AUTH=true # Bypass auth validation
DEV_USER_ID=your-dev-user-id # Custom dev user ID
NODE_ENV=development # Enable dev features
```
### Best Practices
- Always use `@UseGuards(JwtAuthGuard)` for protected routes
- Extract user data with `@CurrentUser()` decorator
- Pass `user.userId` to services, not entire user object
- Validate ownership/permissions in services, not controllers
- Use development bypass for local testing without auth setup
- Set `MANA_CORE_AUTH_URL` in environment files
- Never bypass auth in production
- Log authentication errors for debugging
- Handle `UnauthorizedException` in frontend with redirects
- Keep token validation logic centralized in guard
### Common Issues
**Issue**: Token validation fails with "Auth service unavailable"
**Solution**: Ensure `mana-core-auth` service is running on port 3001
**Issue**: User data is undefined in controller
**Solution**: Make sure `@UseGuards(JwtAuthGuard)` is applied
**Issue**: Development bypass not working
**Solution**: Check `NODE_ENV=development` and `DEV_BYPASS_AUTH=true` are set
**Issue**: Token appears valid but validation fails
**Solution**: Verify token was issued by Mana Core Auth, not another service
## Type Definitions
### CurrentUserData
```typescript
interface CurrentUserData {
userId: string; // Unique user identifier
email: string; // User's email address
role: UserRole; // User role (user, admin, etc.)
sessionId: string; // Session identifier
}
```
### TokenValidationResponse
```typescript
interface TokenValidationResponse {
valid: boolean; // Whether token is valid
payload?: JWTPayload; // Decoded JWT payload if valid
error?: string; // Error message if invalid
}
```
### JWTPayload
```typescript
interface JWTPayload {
sub: string; // Subject (user ID)
email: string; // User email
role: UserRole; // User role
sessionId?: string; // Session ID
sid?: string; // Alternative session ID field
iat?: number; // Issued at timestamp
exp?: number; // Expiration timestamp
}
```
## Notes
- All authentication flows centralized via Mana Core Auth service
- Guards are stateless and validate on every request
- Development bypass useful for testing without full auth setup
- Type-safe with full TypeScript support
- Compatible with NestJS 10 and 11
- Works with both HTTP and WebSocket contexts
- Follows NestJS best practices for guards and decorators
- No database queries - pure token validation
- Fast validation via HTTP call to auth service

View file

@ -0,0 +1,21 @@
# Memory: @manacore/shared-nestjs-auth
## Recent Changes
<!-- Track recent modifications, updates, and decisions made to this package -->
## Known Issues
<!-- Document any known bugs, limitations, or technical debt -->
## Future Enhancements
<!-- Ideas for future improvements or features -->
## Usage Notes
<!-- Record specific usage patterns discovered during development -->
## Integration Examples
<!-- Document real-world integration examples from apps using this package -->

View file

@ -0,0 +1,489 @@
# @manacore/shared-profile-ui Agent
## Module Information
**Package**: `@manacore/shared-profile-ui`
**Type**: Profile UI Component Library
**Framework**: Svelte 5 (Runes Mode)
**Purpose**: Shared user profile display components for SvelteKit applications
## Identity
I am the Shared Profile UI Agent. I provide reusable Svelte 5 components for displaying user profile information consistently across all ManaCore applications. I handle profile pages, user data display, and profile-related actions.
## Expertise
- **Svelte 5 Runes**: Modern Svelte 5 syntax with `$state`, `$derived`, `$effect`
- **Profile Display**: Consistent user profile presentation
- **User Data**: Structured user profile data types
- **Profile Actions**: Edit, settings, logout actions
- **Responsive Design**: Mobile-friendly profile layouts
- **Accessibility**: Semantic HTML and ARIA labels
## Code Structure
```
src/
├── ProfilePage.svelte # Main profile page component
├── types.ts # TypeScript type definitions
└── index.ts # Package exports
```
### Main Files
#### index.ts
```typescript
/**
* Shared profile UI components for Manacore monorepo
*
* This package contains Svelte 5 components for displaying
* user profile information.
*/
// Pages
export { default as ProfilePage } from './ProfilePage.svelte';
// Types
export type { UserProfile, ProfileActions } from './types';
```
#### types.ts
Defines core types for user profiles and actions:
- `UserProfile`: User profile data structure
- `ProfileActions`: Available profile actions (edit, settings, logout, etc.)
#### ProfilePage.svelte
Complete profile page component with:
- User avatar/photo display
- Profile information (name, email, bio, etc.)
- Profile actions (edit, settings)
- Responsive layout
- Customizable slots for additional content
## Key Patterns
### ProfilePage Component
```svelte
<script lang="ts">
import { ProfilePage } from '@manacore/shared-profile-ui';
import type { UserProfile, ProfileActions } from '@manacore/shared-profile-ui';
const profile: UserProfile = {
id: '123',
name: 'Jane Doe',
email: 'jane@example.com',
avatar: '/avatars/jane.jpg',
bio: 'Software developer and designer',
createdAt: new Date('2024-01-01'),
};
const actions: ProfileActions = {
onEdit: () => console.log('Edit profile'),
onSettings: () => console.log('Open settings'),
onLogout: () => console.log('Logout'),
};
</script>
<ProfilePage {profile} {actions} />
```
### Custom Profile Display
```svelte
<script lang="ts">
import { ProfilePage } from '@manacore/shared-profile-ui';
import type { UserProfile } from '@manacore/shared-profile-ui';
const profile: UserProfile = {
id: '123',
name: 'Jane Doe',
email: 'jane@example.com',
avatar: '/avatars/jane.jpg',
};
</script>
<ProfilePage {profile}>
<!-- Additional content slot -->
<div slot="extra">
<h3>Recent Activity</h3>
<ul>
<li>Logged in today</li>
<li>Updated profile yesterday</li>
</ul>
</div>
</ProfilePage>
```
### Profile with Actions
```svelte
<script lang="ts">
import { ProfilePage } from '@manacore/shared-profile-ui';
import { goto } from '$app/navigation';
let profile = $state({
id: '123',
name: 'Jane Doe',
email: 'jane@example.com',
});
function handleEdit() {
goto('/profile/edit');
}
function handleSettings() {
goto('/settings');
}
function handleLogout() {
// Logout logic
}
</script>
<ProfilePage
{profile}
actions={{
onEdit: handleEdit,
onSettings: handleSettings,
onLogout: handleLogout,
}}
/>
```
## Integration Points
### Dependencies
This package has minimal dependencies:
- Svelte 5 components only
- May use shared-ui components internally
### Peer Dependencies
- **svelte**: ^5.0.0
### Used By
- User profile pages in all SvelteKit applications
- Account settings pages
- User directory/listing pages
- Admin user management interfaces
### Integration with Auth
```svelte
<script lang="ts">
import { ProfilePage } from '@manacore/shared-profile-ui';
import type { UserProfile } from '@manacore/shared-profile-ui';
import { page } from '$app/stores';
// Get user from auth context
const user = $derived($page.data.user);
const profile: UserProfile = $derived({
id: user.id,
name: user.name,
email: user.email,
avatar: user.avatar,
});
</script>
<ProfilePage {profile} />
```
### Integration with shared-ui
```svelte
<script lang="ts">
import { ProfilePage } from '@manacore/shared-profile-ui';
import { Button, Card } from '@manacore/shared-ui';
import { PencilSimple } from '@manacore/shared-icons';
let profile = $state({
id: '123',
name: 'Jane Doe',
email: 'jane@example.com',
});
</script>
<Card>
<ProfilePage {profile}>
<div slot="actions">
<Button variant="primary" onclick={() => console.log('Edit')}>
<PencilSimple size={16} />
Edit Profile
</Button>
</div>
</ProfilePage>
</Card>
```
## How to Use
### Installation
This package is internal to the monorepo:
```json
{
"dependencies": {
"@manacore/shared-profile-ui": "workspace:*"
}
}
```
### Basic Usage
```svelte
<script lang="ts">
import { ProfilePage } from '@manacore/shared-profile-ui';
import type { UserProfile, ProfileActions } from '@manacore/shared-profile-ui';
const profile: UserProfile = {
id: 'user-123',
name: 'John Smith',
email: 'john@example.com',
avatar: 'https://example.com/avatars/john.jpg',
bio: 'Full-stack developer passionate about web technologies',
location: 'San Francisco, CA',
website: 'https://johnsmith.dev',
createdAt: new Date('2023-01-15'),
};
const actions: ProfileActions = {
onEdit: () => {
console.log('Navigate to edit profile');
},
onSettings: () => {
console.log('Navigate to settings');
},
onLogout: async () => {
console.log('Logout user');
},
};
</script>
<ProfilePage {profile} {actions} />
```
### With SvelteKit Page Data
```svelte
<!-- src/routes/profile/+page.svelte -->
<script lang="ts">
import { ProfilePage } from '@manacore/shared-profile-ui';
import type { PageData } from './$types';
let { data }: { data: PageData } = $props();
const profile = $derived({
id: data.user.id,
name: data.user.name,
email: data.user.email,
avatar: data.user.avatar,
bio: data.user.bio,
createdAt: new Date(data.user.createdAt),
});
const actions = {
onEdit: () => {
// Navigate to edit page
},
onSettings: () => {
// Navigate to settings
},
onLogout: async () => {
// Handle logout
},
};
</script>
<ProfilePage {profile} {actions} />
```
### Extending with Custom Content
```svelte
<script lang="ts">
import { ProfilePage } from '@manacore/shared-profile-ui';
import { Card } from '@manacore/shared-ui';
let profile = $state({
id: '123',
name: 'Jane Doe',
email: 'jane@example.com',
});
</script>
<ProfilePage {profile}>
<!-- Add custom sections using slots -->
<div slot="stats">
<Card>
<h3>Statistics</h3>
<p>Posts: 42</p>
<p>Followers: 1,234</p>
</Card>
</div>
<div slot="activity">
<Card>
<h3>Recent Activity</h3>
<ul>
<li>Posted a new article</li>
<li>Updated profile picture</li>
</ul>
</Card>
</div>
</ProfilePage>
```
### Type-Safe Profile Updates
```svelte
<script lang="ts">
import { ProfilePage } from '@manacore/shared-profile-ui';
import type { UserProfile } from '@manacore/shared-profile-ui';
let profile = $state<UserProfile>({
id: '123',
name: 'Jane Doe',
email: 'jane@example.com',
});
async function updateProfile(updates: Partial<UserProfile>) {
// API call to update profile
const response = await fetch('/api/profile', {
method: 'PATCH',
body: JSON.stringify(updates),
});
if (response.ok) {
profile = { ...profile, ...updates };
}
}
const actions = {
onEdit: () => updateProfile({ name: 'Jane Smith' }),
};
</script>
<ProfilePage {profile} {actions} />
```
## Best Practices
1. **Use Type Definitions**: Always import and use `UserProfile` and `ProfileActions` types
2. **Svelte 5 Runes**: Use `$state`, `$derived`, and `$props` for reactive data
3. **Minimal Data**: Only pass necessary profile data to components
4. **Action Handlers**: Provide clear action handlers for profile operations
5. **Loading States**: Handle loading states when fetching profile data
6. **Error Handling**: Gracefully handle missing or invalid profile data
7. **Avatar Fallbacks**: Provide fallback for missing avatar images
8. **Privacy**: Respect user privacy settings for profile visibility
9. **Responsive Design**: Ensure profile displays well on all screen sizes
10. **Accessibility**: Use semantic HTML and proper ARIA labels
## Type Definitions
### UserProfile
```typescript
interface UserProfile {
id: string;
name: string;
email: string;
avatar?: string;
bio?: string;
location?: string;
website?: string;
phone?: string;
createdAt?: Date;
updatedAt?: Date;
// Add custom fields as needed
[key: string]: any;
}
```
### ProfileActions
```typescript
interface ProfileActions {
onEdit?: () => void;
onSettings?: () => void;
onLogout?: () => void | Promise<void>;
onDelete?: () => void | Promise<void>;
// Add custom actions as needed
[key: string]: (() => void | Promise<void>) | undefined;
}
```
## Component Slots
### ProfilePage Slots
- **default**: Additional content below profile information
- **header**: Custom header content
- **actions**: Custom action buttons
- **stats**: Statistics or metrics section
- **activity**: Recent activity section
- **extra**: Any additional custom content
## Common Use Cases
1. **User Profile Page**: Display logged-in user's profile
2. **Public Profile**: Show public user profile to other users
3. **Admin User View**: Admin interface for viewing user details
4. **Account Settings**: Part of account settings interface
5. **User Directory**: Profile cards in user listing
6. **Team Members**: Display team member profiles
## Integration Examples
### With Authentication
```svelte
<script lang="ts">
import { ProfilePage } from '@manacore/shared-profile-ui';
import { getContext } from 'svelte';
const auth = getContext('auth');
const profile = $derived(auth.user);
</script>
{#if profile}
<ProfilePage {profile} />
{:else}
<p>Loading profile...</p>
{/if}
```
### With API Data
```svelte
<script lang="ts">
import { ProfilePage } from '@manacore/shared-profile-ui';
import type { UserProfile } from '@manacore/shared-profile-ui';
let profile = $state<UserProfile | null>(null);
let loading = $state(true);
async function loadProfile() {
const response = await fetch('/api/profile');
profile = await response.json();
loading = false;
}
$effect(() => {
loadProfile();
});
</script>
{#if loading}
<p>Loading...</p>
{:else if profile}
<ProfilePage {profile} />
{:else}
<p>Profile not found</p>
{/if}
```

View file

@ -0,0 +1,36 @@
# @manacore/shared-profile-ui Memory
## Changelog
### Recent Changes
- No changes recorded yet
## Common Issues & Solutions
### Issue Template
**Problem**:
**Solution**:
**Date**:
## Patterns & Learnings
### Profile Display Patterns
- No patterns documented yet
### Integration Patterns
- No patterns documented yet
## Technical Decisions
### Decision Template
**Decision**:
**Reason**:
**Date**:
**Impact**:
## Notes
- This memory file is maintained by agents working on @manacore/shared-profile-ui
- Add significant changes, decisions, and learnings here
- Document common profile customizations
- Note integration patterns with auth systems

View file

@ -0,0 +1,657 @@
# @manacore/shared-splitscreen Agent
## Module Information
**Package**: `@manacore/shared-splitscreen`
**Type**: Split-Screen Panel System
**Framework**: Svelte 5 (Runes Mode)
**Purpose**: Enable side-by-side app viewing with resizable panels using iFrames
## Identity
I am the Shared Splitscreen Agent. I provide a sophisticated split-screen panel system that allows ManaCore applications to display two apps side-by-side using iFrames. I handle panel management, resizing, state persistence, URL state synchronization, and provide a complete component library for split-screen layouts.
## Expertise
- **Split-Screen UI**: Resizable two-panel layout system
- **iFrame Management**: Loading and controlling apps in iFrames
- **State Management**: Svelte 5 stores for panel state
- **URL State Sync**: Persist panel state in URL query parameters
- **Local Storage**: Save panel preferences per app
- **Responsive Design**: Mobile-aware with breakpoint handling
- **Panel Controls**: Open, close, swap, resize operations
- **Drag Resize**: Interactive divider with drag-to-resize
- **Type Safety**: Complete TypeScript type definitions
## Code Structure
```
src/
├── components/ # UI components
│ ├── SplitPaneContainer.svelte # Main container
│ ├── AppPanel.svelte # Individual panel
│ ├── PanelControls.svelte # Control buttons
│ └── ResizeHandle.svelte # Draggable divider
├── stores/
│ └── split-panel.svelte.ts # State management store
├── utils/ # Utility functions
│ ├── index.ts # Main exports
│ ├── local-storage.ts # localStorage helpers
│ └── url-state.ts # URL state helpers
├── types.ts # Type definitions
└── index.ts # Package exports
```
## Key Patterns
### Store-Based State Management
The package uses a Svelte 5 store (with runes) for managing split-screen state:
```typescript
import { createSplitPanelStore } from '@manacore/shared-splitscreen';
const splitPanel = createSplitPanelStore({
currentAppId: 'calendar', // Current app identifier
storageKey: 'split-panel-state', // localStorage key
availableApps: [
{ id: 'todo', name: 'Todo', baseUrl: '/todo', icon: 'check-square' },
{ id: 'contacts', name: 'Contacts', baseUrl: '/contacts', icon: 'users' },
],
});
```
### Panel Operations
```typescript
// Open a panel
splitPanel.openPanel({
appId: 'todo',
url: '/todo?view=today',
name: 'Todo App',
});
// Close the right panel
splitPanel.closePanel();
// Swap panels (left becomes right, right becomes left)
splitPanel.swapPanels();
// Resize divider
splitPanel.setDividerPosition(60); // 60% left, 40% right
```
### URL State Synchronization
```typescript
import { parseUrlState, updateUrlState } from '@manacore/shared-splitscreen';
// Read state from URL
const urlState = parseUrlState(window.location.search);
// { panel: 'todo', split: 50 }
// Update URL when panel changes
updateUrlState({ panel: 'contacts', split: 60 });
// Updates URL to ?panel=contacts&split=60
```
### Local Storage Persistence
```typescript
import { savePanelState, loadPanelState } from '@manacore/shared-splitscreen';
// Save current panel state
savePanelState('calendar', {
isActive: true,
rightPanel: { appId: 'todo', url: '/todo', name: 'Todo' },
dividerPosition: 50,
});
// Load saved state
const savedState = loadPanelState('calendar');
```
### Context-Based API
```typescript
import { setSplitPanelContext, getSplitPanelContext } from '@manacore/shared-splitscreen';
// In parent component
const store = createSplitPanelStore({ currentAppId: 'calendar' });
setSplitPanelContext(store);
// In child components
const splitPanel = getSplitPanelContext();
```
## Component Usage
### SplitPaneContainer
Main container component that manages the split layout:
```svelte
<script lang="ts">
import { SplitPaneContainer } from '@manacore/shared-splitscreen';
import type { AppDefinition } from '@manacore/shared-splitscreen';
const availableApps: AppDefinition[] = [
{ id: 'todo', name: 'Todo', baseUrl: '/todo', icon: 'check-square', color: '#3B82F6' },
{ id: 'calendar', name: 'Calendar', baseUrl: '/calendar', icon: 'calendar', color: '#10B981' },
{ id: 'contacts', name: 'Contacts', baseUrl: '/contacts', icon: 'users', color: '#F59E0B' },
];
</script>
<SplitPaneContainer currentAppId="calendar" {availableApps}>
<!-- Left panel content (your main app) -->
<div slot="left">
<h1>Calendar App</h1>
<p>Your calendar content here</p>
</div>
<!-- Optional: Custom panel controls -->
<div slot="controls">
<button>Custom Control</button>
</div>
</SplitPaneContainer>
```
### AppPanel
Individual panel for displaying an app in an iFrame:
```svelte
<script lang="ts">
import { AppPanel } from '@manacore/shared-splitscreen';
</script>
<AppPanel
appId="todo"
url="/todo?view=today"
name="Todo App"
onClose={() => console.log('Panel closed')}
/>
```
### PanelControls
Control buttons for panel operations:
```svelte
<script lang="ts">
import { PanelControls } from '@manacore/shared-splitscreen';
import { getSplitPanelContext } from '@manacore/shared-splitscreen';
const splitPanel = getSplitPanelContext();
</script>
<PanelControls
availableApps={[
{ id: 'todo', name: 'Todo', baseUrl: '/todo' },
{ id: 'contacts', name: 'Contacts', baseUrl: '/contacts' },
]}
onOpenApp={(app) => splitPanel.openPanel({
appId: app.id,
url: app.baseUrl,
name: app.name,
})}
onClose={() => splitPanel.closePanel()}
onSwap={() => splitPanel.swapPanels()}
/>
```
### ResizeHandle
Draggable divider for resizing panels:
```svelte
<script lang="ts">
import { ResizeHandle } from '@manacore/shared-splitscreen';
let dividerPosition = $state(50);
</script>
<div class="split-container">
<div class="left-panel" style="width: {dividerPosition}%">
Left content
</div>
<ResizeHandle
bind:position={dividerPosition}
onDragEnd={(position) => console.log('Final position:', position)}
/>
<div class="right-panel" style="width: {100 - dividerPosition}%">
Right content
</div>
</div>
```
## Type Definitions
### Core Types
```typescript
// Panel configuration
interface PanelConfig {
appId: string; // Unique app identifier
url: string; // Full URL for iFrame
name?: string; // Display name
}
// Split-screen state
interface SplitScreenState {
isActive: boolean; // Is split mode active?
rightPanel: PanelConfig | null; // Right panel config
dividerPosition: number; // Position (20-80)
}
// App definition
interface AppDefinition {
id: string; // Unique identifier
name: string; // Display name
baseUrl: string; // Base URL
icon?: string; // Icon name
color?: string; // Theme color
}
// Panel event
interface PanelEvent {
type: 'open' | 'close' | 'swap' | 'resize';
panel?: PanelConfig;
dividerPosition?: number;
}
// Storage configuration
interface StorageConfig {
prefix: string; // localStorage key prefix
currentAppId: string; // Current app ID for scoped storage
}
// URL state
interface UrlState {
panel?: string; // App ID for right panel
split?: number; // Divider position
}
```
### Constants
```typescript
// Divider constraints
const DIVIDER_CONSTRAINTS = {
MIN: 20, // Minimum 20% for left panel
MAX: 80, // Maximum 80% for left panel
DEFAULT: 50, // Default 50/50 split
} as const;
// Mobile breakpoint (disable split-screen below this)
const MOBILE_BREAKPOINT = 1024; // pixels
```
## Integration Points
### Dependencies
- **svelte**: ^5.0.0 (Svelte 5 components and stores)
### Peer Dependencies
- **svelte**: ^5.0.0
### Used By
- Multi-app dashboard interfaces
- Productivity apps with side-by-side views
- Calendar + Todo integration
- Contact + Email integration
- Any app needing split-screen functionality
### Integration with SvelteKit
```svelte
<!-- src/routes/+layout.svelte -->
<script lang="ts">
import { SplitPaneContainer } from '@manacore/shared-splitscreen';
import { page } from '$app/stores';
const apps = [
{ id: 'calendar', name: 'Calendar', baseUrl: '/calendar', icon: 'calendar' },
{ id: 'todo', name: 'Todo', baseUrl: '/todo', icon: 'check-square' },
{ id: 'contacts', name: 'Contacts', baseUrl: '/contacts', icon: 'users' },
];
const currentAppId = $derived($page.url.pathname.split('/')[1] || 'calendar');
</script>
<SplitPaneContainer {currentAppId} availableApps={apps}>
<div slot="left">
<slot /> <!-- Main app content -->
</div>
</SplitPaneContainer>
```
## How to Use
### Installation
This package is internal to the monorepo:
```json
{
"dependencies": {
"@manacore/shared-splitscreen": "workspace:*"
}
}
```
### Basic Setup
```svelte
<script lang="ts">
import {
SplitPaneContainer,
createSplitPanelStore,
setSplitPanelContext,
} from '@manacore/shared-splitscreen';
import type { AppDefinition } from '@manacore/shared-splitscreen';
const apps: AppDefinition[] = [
{ id: 'todo', name: 'Todo', baseUrl: '/todo', icon: 'check-square' },
{ id: 'calendar', name: 'Calendar', baseUrl: '/calendar', icon: 'calendar' },
{ id: 'contacts', name: 'Contacts', baseUrl: '/contacts', icon: 'users' },
];
// Create and set context
const store = createSplitPanelStore({
currentAppId: 'calendar',
availableApps: apps,
});
setSplitPanelContext(store);
</script>
<SplitPaneContainer currentAppId="calendar" availableApps={apps}>
<div slot="left">
<h1>My Calendar App</h1>
<!-- Your app content -->
</div>
</SplitPaneContainer>
```
### With URL State Persistence
```svelte
<script lang="ts">
import {
createSplitPanelStore,
parseUrlState,
updateUrlState,
} from '@manacore/shared-splitscreen';
import { browser } from '$app/environment';
// Parse initial state from URL
const urlState = browser ? parseUrlState(window.location.search) : {};
const store = createSplitPanelStore({
currentAppId: 'calendar',
availableApps: apps,
});
// Restore state from URL
if (urlState.panel) {
const app = apps.find(a => a.id === urlState.panel);
if (app) {
store.openPanel({
appId: app.id,
url: app.baseUrl,
name: app.name,
});
}
}
// Sync URL when state changes
$effect(() => {
if (browser) {
updateUrlState({
panel: store.state.rightPanel?.appId,
split: store.state.dividerPosition,
});
}
});
</script>
```
### With Local Storage
```svelte
<script lang="ts">
import {
createSplitPanelStore,
loadPanelState,
savePanelState,
} from '@manacore/shared-splitscreen';
const currentAppId = 'calendar';
// Load saved state
const savedState = loadPanelState(currentAppId);
const store = createSplitPanelStore({
currentAppId,
availableApps: apps,
initialState: savedState,
});
// Save state on changes
$effect(() => {
savePanelState(currentAppId, store.state);
});
</script>
```
### Custom Panel Controls
```svelte
<script lang="ts">
import {
SplitPaneContainer,
getSplitPanelContext,
} from '@manacore/shared-splitscreen';
import { Button } from '@manacore/shared-ui';
import { X, ArrowsLeftRight } from '@manacore/shared-icons';
const splitPanel = getSplitPanelContext();
const isActive = $derived(splitPanel.state.isActive);
function openTodo() {
splitPanel.openPanel({
appId: 'todo',
url: '/todo',
name: 'Todo',
});
}
</script>
<SplitPaneContainer currentAppId="calendar" availableApps={apps}>
<div slot="left">
<div class="toolbar">
<Button onclick={openTodo}>Open Todo</Button>
{#if isActive}
<Button onclick={() => splitPanel.swapPanels()}>
<ArrowsLeftRight size={16} />
Swap
</Button>
<Button variant="ghost" onclick={() => splitPanel.closePanel()}>
<X size={16} />
Close Panel
</Button>
{/if}
</div>
<!-- Main content -->
</div>
</SplitPaneContainer>
```
### Responsive Behavior
```svelte
<script lang="ts">
import { SplitPaneContainer } from '@manacore/shared-splitscreen';
import { MOBILE_BREAKPOINT } from '@manacore/shared-splitscreen';
let windowWidth = $state(0);
// Detect window size
$effect(() => {
if (browser) {
windowWidth = window.innerWidth;
window.addEventListener('resize', () => {
windowWidth = window.innerWidth;
});
}
});
const isMobile = $derived(windowWidth < MOBILE_BREAKPOINT);
</script>
{#if isMobile}
<!-- Mobile: No split-screen -->
<div>
<slot />
</div>
{:else}
<!-- Desktop: Enable split-screen -->
<SplitPaneContainer currentAppId="calendar" availableApps={apps}>
<div slot="left">
<slot />
</div>
</SplitPaneContainer>
{/if}
```
## Best Practices
1. **Use Context API**: Always use `setSplitPanelContext` and `getSplitPanelContext` for store access
2. **Persist State**: Combine URL state and localStorage for best UX
3. **Responsive Design**: Disable split-screen on mobile (< 1024px)
4. **Constrain Divider**: Respect MIN/MAX constraints (20-80%)
5. **Loading States**: Show loading indicator while iFrame loads
6. **Error Handling**: Handle iFrame loading errors gracefully
7. **Type Safety**: Use TypeScript types for all panel operations
8. **Performance**: Lazy load panels, unload when closed
9. **Accessibility**: Provide keyboard shortcuts for panel operations
10. **Clear Actions**: Provide obvious UI for opening/closing panels
## Store API
### createSplitPanelStore
```typescript
const store = createSplitPanelStore({
currentAppId: string; // Required: Current app ID
availableApps?: AppDefinition[]; // Optional: Available apps
initialState?: SplitScreenState; // Optional: Initial state
storageKey?: string; // Optional: localStorage key
});
```
### Store Methods
```typescript
store.openPanel(config: PanelConfig): void
store.closePanel(): void
store.swapPanels(): void
store.setDividerPosition(position: number): void
store.reset(): void
```
### Store State
```typescript
store.state // Access current state
.isActive: boolean
.rightPanel: PanelConfig | null
.dividerPosition: number
```
## Utility Functions
### URL State
```typescript
parseUrlState(search: string): UrlState
updateUrlState(state: UrlState): void
clearUrlState(): void
getCurrentUrlState(): UrlState
```
### Local Storage
```typescript
savePanelState(appId: string, state: SplitScreenState): void
loadPanelState(appId: string): SplitScreenState | null
clearPanelState(appId: string): void
createStorageConfig(currentAppId: string, prefix?: string): StorageConfig
```
## Common Use Cases
1. **Calendar + Todo**: View calendar and todo list side-by-side
2. **Email + Contacts**: Read email while browsing contacts
3. **Dashboard + Analytics**: Main dashboard with analytics panel
4. **Code + Preview**: Code editor with live preview
5. **Chat + Video**: Chat interface with video call panel
6. **Notes + Reference**: Note-taking with reference material
## Advanced Patterns
### Dynamic App Loading
```svelte
<script lang="ts">
import { getSplitPanelContext } from '@manacore/shared-splitscreen';
const splitPanel = getSplitPanelContext();
function openAppWithQuery(appId: string, query: Record<string, string>) {
const app = apps.find(a => a.id === appId);
if (!app) return;
const params = new URLSearchParams(query).toString();
const url = `${app.baseUrl}?${params}`;
splitPanel.openPanel({
appId: app.id,
url,
name: app.name,
});
}
// Usage
openAppWithQuery('todo', { view: 'today', filter: 'priority' });
</script>
```
### Cross-Panel Communication
```svelte
<script lang="ts">
import { getSplitPanelContext } from '@manacore/shared-splitscreen';
const splitPanel = getSplitPanelContext();
function sendMessageToPanel(message: any) {
const iframe = document.querySelector('iframe');
if (iframe?.contentWindow) {
iframe.contentWindow.postMessage(message, '*');
}
}
// Listen for messages from iFrame
window.addEventListener('message', (event) => {
console.log('Message from panel:', event.data);
});
</script>
```

View file

@ -0,0 +1,37 @@
# @manacore/shared-splitscreen Memory
## Changelog
### Recent Changes
- No changes recorded yet
## Common Issues & Solutions
### Issue Template
**Problem**:
**Solution**:
**Date**:
## Patterns & Learnings
### Split-Screen Patterns
- No patterns documented yet
### State Management Patterns
- No patterns documented yet
## Technical Decisions
### Decision Template
**Decision**:
**Reason**:
**Date**:
**Impact**:
## Notes
- This memory file is maintained by agents working on @manacore/shared-splitscreen
- Add significant changes, decisions, and learnings here
- Document common split-screen use cases
- Note cross-app communication patterns
- Track performance optimizations for iFrame loading

View file

@ -0,0 +1,437 @@
# Shared Storage Agent
## Module Information
**Package**: `@manacore/shared-storage`
**Version**: 0.1.0
**Type**: TypeScript compiled library (dist/)
**Dependencies**:
- `@aws-sdk/client-s3` 3.700.0
- `@aws-sdk/s3-request-presigner` 3.700.0
**Build**: Compiled to CommonJS in `dist/` directory
## Identity
I am the Shared Storage Agent, responsible for providing S3-compatible object storage capabilities across all ManaCore backend services. I provide a unified interface for MinIO (local development) and Hetzner Object Storage (production), managing file uploads, downloads, presigned URLs, and a centralized bucket structure for all applications.
## Expertise
- **S3 Compatibility**: Works with MinIO, Hetzner, AWS S3, and any S3-compatible service
- **Unified Bucket Architecture**: Single bucket with `{userId}/{appName}/` folder structure
- **File Operations**: Upload, download, delete, list with presigned URLs
- **Type Safety**: Full TypeScript types for all operations
- **Path Management**: Automatic file key generation with UUIDs
- **MIME Type Detection**: Automatic content type detection from extensions
- **Environment Configuration**: Auto-configuration for development and production
## Code Structure
```
src/
├── index.ts # Main export barrel
├── types.ts # TypeScript types and constants
├── client.ts # S3 StorageClient class
├── factory.ts # Factory functions for creating clients
└── utils.ts # File key generation and validation utilities
```
## Key Patterns
### 1. Unified Bucket Architecture
Single bucket for all ManaCore apps with organized folder structure:
```
manacore-storage/
├── {userId}/
│ ├── picture/
│ │ ├── {uuid}.jpg
│ │ └── {uuid}.png
│ ├── chat/
│ │ ├── attachments/{uuid}.pdf
│ │ └── avatars/{uuid}.jpg
│ ├── contacts/
│ │ └── {uuid}.vcf
│ └── ...
```
**Benefits:**
- Single bucket simplifies management and costs
- User-scoped folders for data isolation
- App-specific subfolders for organization
- Easy to list all files for a user/app
### 2. Storage Client Pattern
Main class for all S3 operations:
```typescript
import { createUnifiedStorage, generateStorageKey, APPS } from '@manacore/shared-storage';
const storage = createUnifiedStorage();
// Upload file
const key = generateStorageKey('user-123', APPS.PICTURE, 'photo.jpg');
const result = await storage.upload(key, imageBuffer, {
contentType: 'image/jpeg',
public: true,
cacheControl: 'max-age=31536000'
});
// result: { key, url?, etag? }
// Download file
const buffer = await storage.download(key);
// Delete file
await storage.delete(key);
// Check existence
const exists = await storage.exists(key);
// List files
const files = await storage.list('user-123/picture/');
// files: [{ key, size, lastModified, etag }]
```
### 3. Presigned URL Pattern
Generate temporary signed URLs for client-side uploads/downloads:
```typescript
// Upload URL (for client-side direct uploads)
const uploadUrl = await storage.getUploadUrl(key, {
expiresIn: 3600 // 1 hour
});
// Client can PUT to this URL
// Download URL (for private files)
const downloadUrl = await storage.getDownloadUrl(key, {
expiresIn: 900 // 15 minutes
});
// Client can GET from this URL
// Public URL (if bucket has public access)
const publicUrl = storage.getPublicUrl(key);
// Direct URL (no expiration)
```
### 4. File Key Generation Pattern
Utility functions for consistent key naming:
```typescript
import { generateStorageKey, generateFileKey, APPS } from '@manacore/shared-storage';
// Unified bucket key (recommended)
const key = generateStorageKey('user-123', APPS.PICTURE, 'photo.jpg');
// => 'user-123/picture/{uuid}.jpg'
const key2 = generateStorageKey('user-123', APPS.CHAT, 'doc.pdf', 'attachments');
// => 'user-123/chat/attachments/{uuid}.pdf'
// Generic file key
const key3 = generateFileKey('image.png', 'folder1', 'folder2');
// => 'folder1/folder2/{uuid}.png'
// User-scoped key
const key4 = generateUserFileKey('user-123', 'avatar.png');
// => 'users/user-123/{uuid}.png'
```
### 5. Environment Configuration Pattern
Auto-configures based on environment:
```typescript
import { getStorageConfig, createStorageClient } from '@manacore/shared-storage';
// Get config (auto-detects MinIO in dev)
const config = getStorageConfig();
// Development: Uses MinIO defaults (localhost:9000)
// Production: Reads from S3_ENDPOINT, S3_ACCESS_KEY, etc.
// Create custom client
const storage = createStorageClient('my-bucket', {
endpoint: 'https://storage.example.com',
region: 'eu-central-1',
accessKeyId: 'key',
secretAccessKey: 'secret'
});
```
## Integration Points
### With Backend Services (NestJS)
Primary use case - file storage in backends:
```typescript
// app.module.ts
import { createUnifiedStorage } from '@manacore/shared-storage';
@Module({
providers: [
{
provide: 'STORAGE_CLIENT',
useValue: createUnifiedStorage()
}
]
})
// service.ts
import { StorageClient, generateStorageKey, APPS } from '@manacore/shared-storage';
@Injectable()
export class ImageService {
constructor(@Inject('STORAGE_CLIENT') private storage: StorageClient) {}
async uploadUserImage(userId: string, file: Buffer, filename: string) {
const key = generateStorageKey(userId, APPS.PICTURE, filename);
const result = await this.storage.upload(key, file, {
contentType: getContentType(filename),
public: true
});
return result;
}
}
```
### With File Upload Utilities
```typescript
import {
getContentType,
validateFileSize,
validateFileExtension,
IMAGE_EXTENSIONS
} from '@manacore/shared-storage';
// Validate before upload
if (!validateFileSize(file.size, 10)) {
throw new Error('File too large (max 10MB)');
}
if (!validateFileExtension(filename, IMAGE_EXTENSIONS)) {
throw new Error('Invalid file type');
}
const contentType = getContentType(filename);
// Auto-detects: 'image/jpeg', 'image/png', etc.
```
### With Frontend (Presigned URLs)
```typescript
// Backend: Generate upload URL
const uploadUrl = await storage.getUploadUrl(key);
// Frontend: Direct upload to S3
await fetch(uploadUrl, {
method: 'PUT',
body: file,
headers: {
'Content-Type': file.type
}
});
```
## Available App Names
```typescript
import { APPS, type AppName } from '@manacore/shared-storage';
APPS.PICTURE // 'picture'
APPS.CHAT // 'chat'
APPS.MANADECK // 'manadeck'
APPS.NUTRIPHI // 'nutriphi'
APPS.PRESI // 'presi'
APPS.CALENDAR // 'calendar'
APPS.CONTACTS // 'contacts'
APPS.STORAGE // 'storage'
APPS.MAIL // 'mail'
APPS.INVENTORY // 'inventory'
APPS.MANACORE // 'manacore'
```
## Environment Variables
### Required (Production)
```bash
S3_ENDPOINT=https://storage.example.com
S3_REGION=eu-central-1
S3_ACCESS_KEY=your-access-key
S3_SECRET_KEY=your-secret-key
```
### Optional
```bash
MANACORE_STORAGE_PUBLIC_URL=https://cdn.example.com/manacore-storage
NODE_ENV=development # Auto-uses MinIO defaults
```
### MinIO Defaults (Development)
```bash
S3_ENDPOINT=http://localhost:9000
S3_REGION=us-east-1
S3_ACCESS_KEY=minioadmin
S3_SECRET_KEY=minioadmin
```
## How to Use
### Installation
This package is internal to the monorepo. Add to dependencies in `package.json`:
```json
{
"dependencies": {
"@manacore/shared-storage": "workspace:*"
}
}
```
### Import Examples
```typescript
// Client and factory
import { StorageClient, createUnifiedStorage, createStorageClient } from '@manacore/shared-storage';
// Utilities
import {
generateStorageKey,
generateFileKey,
generateUserFileKey,
getContentType,
validateFileSize,
validateFileExtension
} from '@manacore/shared-storage';
// Constants
import { UNIFIED_BUCKET, APPS, IMAGE_EXTENSIONS, DOCUMENT_EXTENSIONS } from '@manacore/shared-storage';
// Types
import type {
StorageConfig,
BucketConfig,
AppName,
UploadOptions,
PresignedUrlOptions,
UploadResult,
FileInfo
} from '@manacore/shared-storage';
```
### Best Practices
#### 1. Use Unified Bucket
Always use the unified bucket structure for consistency:
```typescript
const storage = createUnifiedStorage();
const key = generateStorageKey(userId, APPS.PICTURE, filename);
```
#### 2. Set Proper Content Types
Always specify content type for proper browser handling:
```typescript
await storage.upload(key, buffer, {
contentType: getContentType(filename),
cacheControl: 'max-age=31536000' // 1 year for immutable files
});
```
#### 3. Validate Before Upload
Validate files before uploading to prevent errors:
```typescript
if (!validateFileSize(file.size, 10)) {
return err('FILE_TOO_LARGE', 'Max size is 10MB');
}
if (!validateFileExtension(filename, IMAGE_EXTENSIONS)) {
return err('INVALID_FILE_TYPE', 'Only images allowed');
}
```
#### 4. Use Presigned URLs for Client Uploads
For large files, use presigned URLs to avoid proxying through backend:
```typescript
// Backend
const uploadUrl = await storage.getUploadUrl(key, { expiresIn: 3600 });
return { uploadUrl, key };
// Frontend
await fetch(uploadUrl, { method: 'PUT', body: file });
```
#### 5. List with Prefix
Use prefixes to list files for specific users/apps:
```typescript
// All files for a user in an app
const files = await storage.list(`${userId}/${APPS.PICTURE}/`);
// All files in a subfolder
const files = await storage.list(`${userId}/${APPS.CHAT}/attachments/`);
```
### Common Use Cases
1. **User Profile Pictures**
```typescript
const key = generateStorageKey(userId, APPS.MANACORE, 'avatar.jpg', 'avatars');
await storage.upload(key, buffer, { contentType: 'image/jpeg', public: true });
```
2. **Chat Attachments**
```typescript
const key = generateStorageKey(userId, APPS.CHAT, filename, 'attachments');
const uploadUrl = await storage.getUploadUrl(key);
// Return uploadUrl to client for direct upload
```
3. **AI-Generated Images**
```typescript
const key = generateStorageKey(userId, APPS.PICTURE, `${promptId}.png`);
await storage.upload(key, imageBuffer, { contentType: 'image/png', public: true });
const publicUrl = storage.getPublicUrl(key);
```
4. **Document Storage**
```typescript
const key = generateStorageKey(userId, APPS.CONTACTS, 'contacts.vcf', 'exports');
await storage.upload(key, vcfBuffer, { contentType: 'text/vcard' });
const downloadUrl = await storage.getDownloadUrl(key, { expiresIn: 900 });
```
## File Validation Constants
```typescript
IMAGE_EXTENSIONS // ['.jpg', '.jpeg', '.png', '.gif', '.webp', '.svg', '.avif']
DOCUMENT_EXTENSIONS // ['.pdf', '.doc', '.docx', '.xls', '.xlsx', '.ppt', '.pptx']
AUDIO_EXTENSIONS // ['.mp3', '.wav', '.ogg', '.m4a']
VIDEO_EXTENSIONS // ['.mp4', '.webm', '.mov', '.avi']
```
## Advanced Operations
### Direct S3 Client Access
For advanced operations not covered by the wrapper:
```typescript
const s3Client = storage.getS3Client();
// Use with @aws-sdk/client-s3 commands
```
### Custom Bucket
For services that need their own bucket:
```typescript
const storage = createStorageClient({
name: 'my-app-bucket',
publicUrl: 'https://cdn.example.com/my-app-bucket'
});
```
## Notes
- **Compiled Package**: This package is compiled to `dist/` before use
- **S3 Compatible**: Works with MinIO, Hetzner, AWS S3, DigitalOcean Spaces, etc.
- **Path Style**: Automatically uses path-style URLs for MinIO/localhost
- **UUID Keys**: All file keys use UUIDs to prevent collisions and expose filenames
- **Public vs Private**: Set `public: true` for public-read ACL, omit for private files
- **Presigned URL Expiration**: Default 3600s (1 hour), configurable per request
- **Buffer Handling**: Upload accepts Buffer, Uint8Array, string, or ReadableStream
- **Error Handling**: AWS SDK throws errors, wrap calls in try/catch or use Result pattern

View file

@ -0,0 +1,16 @@
# Shared Storage Memory
## Recent Changes
<!-- Track recent modifications, bug fixes, and enhancements -->
## Known Issues
<!-- Document any known bugs or limitations -->
## Bucket Structure
<!-- Document the actual bucket structure in production -->
## Upload Patterns
<!-- Track common upload patterns and optimizations -->
## Integration Notes
<!-- Notes about how this package integrates with other packages -->

View file

@ -0,0 +1,395 @@
# Shared Stores Agent
## Module Information
**Package**: `@manacore/shared-stores`
**Version**: 1.0.0
**Type**: ESM Svelte 5 store library
**Dependencies**:
- `svelte` 5.0.0 (peer)
- `@manacore/shared-auth` (workspace)
## Identity
I am the Shared Stores Agent, responsible for providing reusable Svelte 5 runes-based state management factories for ManaCore web applications. I create type-safe, reactive stores for common UI patterns like toasts, navigation, and theming using Svelte 5's modern reactivity system.
## Expertise
- **Svelte 5 Runes**: Modern reactivity with `$state`, `$derived`, `$effect`
- **Toast Notifications**: Message queue with auto-dismiss
- **Navigation State**: Menu/sidebar with persistence
- **Theme Management**: Dark/light mode with system preference
- **Factory Pattern**: Reusable store creators for consistent behavior
- **LocalStorage Persistence**: Optional state persistence across sessions
## Code Structure
```
src/
├── index.ts # Main export barrel
├── toast.svelte.ts # Toast notification store factory
├── navigation.svelte.ts # Navigation state store factory
└── theme.svelte.ts # Theme management store factory
```
## Key Patterns
### 1. Factory Pattern with Svelte 5 Runes
All stores use factory functions that create isolated instances with runes:
```typescript
// Factory creates independent store instance
export function createToastStore(config?: ToastStoreConfig): ToastStore {
let toasts = $state<Toast[]>([]);
// Getters expose reactive state
return {
get toasts() { return toasts; },
show: (msg, type, duration) => { /* ... */ },
dismiss: (id) => { /* ... */ }
};
}
```
### 2. Toast Store Pattern
Queue-based notification system with auto-dismiss:
```typescript
import { createToastStore } from '@manacore/shared-stores';
const toast = createToastStore({
defaultDuration: 5000,
maxToasts: 5
});
// Show notifications
toast.success('Saved successfully!');
toast.error('Failed to save', 10000);
toast.info('Processing...');
toast.warning('Please review');
// Custom toast
toast.show('Custom message', 'info', 3000);
// Manual dismiss
toast.dismiss(toastId);
// Clear all
toast.clear();
// Access reactive state
$derived(toast.toasts.length > 0);
```
**Types:**
- `ToastType`: 'success' | 'error' | 'info' | 'warning'
- `Toast`: { id, type, message, duration? }
- `ToastStore`: Full interface with methods and reactive state
### 3. Navigation Store Pattern
Menu and sidebar state management with persistence:
```typescript
import { createNavigationStore } from '@manacore/shared-stores';
const navigation = createNavigationStore({
initialItems: [
{ href: '/dashboard', label: 'Dashboard', icon: 'home' },
{ href: '/settings', label: 'Settings', icon: 'settings', badge: '2' }
],
storageKey: 'app-nav',
defaultSidebarMode: false,
defaultCollapsed: false
});
// Set items dynamically
navigation.setItems([...newItems]);
// Control menu state
navigation.open();
navigation.close();
navigation.toggle();
// Sidebar mode (desktop persistent sidebar)
navigation.setSidebarMode(true);
// Collapse state (minimized sidebar)
navigation.setCollapsed(true);
// Reactive state
$derived(navigation.isOpen);
$derived(navigation.isSidebarMode);
$derived(navigation.isCollapsed);
$derived(navigation.items);
```
**Types:**
- `NavigationItem`: { href, label, icon?, badge?, children? }
- `NavigationStore`: Full interface with state and methods
**LocalStorage Keys (if storageKey provided):**
- `{storageKey}-sidebar`: Sidebar mode preference
- `{storageKey}-collapsed`: Collapsed state preference
### 4. Theme Store Pattern
Dark/light mode with system preference detection:
```typescript
import { createThemeStore } from '@manacore/shared-stores';
const theme = createThemeStore({
storagePrefix: 'theme',
defaultMode: 'system',
defaultVariant: 'default',
darkClass: 'dark',
variantAttribute: 'data-theme'
});
// Initialize (call in onMount or +layout.svelte)
const cleanup = theme.initialize();
// Set mode
theme.setMode('dark'); // Always dark
theme.setMode('light'); // Always light
theme.setMode('system'); // Follow system preference
// Toggle between light/dark
theme.toggle();
// Set theme variant (for multi-theme support)
theme.setVariant('ocean');
theme.setVariant('sunset');
// Reactive state
$derived(theme.isDark); // true/false based on effective theme
$derived(theme.mode); // 'light' | 'dark' | 'system'
$derived(theme.variant); // Current variant
```
**Types:**
- `ThemeMode`: 'light' | 'dark' | 'system'
- `ThemeStore`: Full interface with state and methods
**Behavior:**
- Adds/removes `darkClass` on `document.documentElement`
- Sets `variantAttribute` on `document.documentElement`
- Listens to system preference changes when mode is 'system'
- Persists mode and variant to localStorage
**LocalStorage Keys:**
- `{storagePrefix}-mode`: Theme mode preference
- `{storagePrefix}-variant`: Theme variant preference
## Integration Points
### With SvelteKit Applications
Primary use case - shared UI state management:
```svelte
<!-- +layout.svelte -->
<script lang="ts">
import { createThemeStore, createToastStore } from '@manacore/shared-stores';
import { onMount } from 'svelte';
const theme = createThemeStore();
const toast = createToastStore();
onMount(() => {
const cleanup = theme.initialize();
return cleanup;
});
</script>
<div class:dark={theme.isDark}>
<slot />
<ToastContainer toasts={toast.toasts} />
</div>
```
### With Shared UI Components
```svelte
<!-- Navigation.svelte -->
<script lang="ts">
import { createNavigationStore } from '@manacore/shared-stores';
const nav = createNavigationStore({
storageKey: 'main-nav',
defaultSidebarMode: true
});
export { nav };
</script>
<nav class:collapsed={nav.isCollapsed}>
{#each nav.items as item}
<a href={item.href}>{item.label}</a>
{/each}
</nav>
```
### With @manacore/shared-auth
```typescript
import { createToastStore } from '@manacore/shared-stores';
import { signIn } from '@manacore/shared-auth';
const toast = createToastStore();
async function handleSignIn(email: string, password: string) {
const result = await signIn(email, password);
if (result.success) {
toast.success('Signed in successfully!');
} else {
toast.error(result.error.message);
}
}
```
## How to Use
### Installation
This package is internal to the monorepo. Add to dependencies in `package.json`:
```json
{
"dependencies": {
"@manacore/shared-stores": "workspace:*",
"svelte": "^5.0.0"
}
}
```
### Import Examples
```typescript
// Import store factories
import {
createToastStore,
createNavigationStore,
createThemeStore,
type Toast,
type ToastType,
type ToastStore,
type NavigationItem,
type NavigationStore,
type ThemeStore,
type ThemeMode
} from '@manacore/shared-stores';
```
### Best Practices
#### 1. Single Instance per App
Create stores once at app root, pass down as needed:
```svelte
<!-- +layout.svelte -->
<script lang="ts">
import { setContext } from 'svelte';
import { createToastStore } from '@manacore/shared-stores';
const toast = createToastStore();
setContext('toast', toast);
</script>
<!-- child component -->
<script lang="ts">
import { getContext } from 'svelte';
import type { ToastStore } from '@manacore/shared-stores';
const toast = getContext<ToastStore>('toast');
</script>
```
#### 2. Initialize Theme Early
Call theme.initialize() as early as possible to prevent flash:
```svelte
<!-- +layout.svelte -->
<script lang="ts">
import { onMount } from 'svelte';
import { createThemeStore } from '@manacore/shared-stores';
const theme = createThemeStore();
// Initialize in onMount, cleanup on destroy
onMount(() => theme.initialize());
</script>
```
#### 3. Type Safety
Use provided TypeScript types for full type safety:
```typescript
import type { Toast, NavigationItem } from '@manacore/shared-stores';
const items: NavigationItem[] = [
{ href: '/', label: 'Home', icon: 'home' },
{
href: '/settings',
label: 'Settings',
children: [
{ href: '/settings/profile', label: 'Profile' }
]
}
];
```
#### 4. Reactive Derivations
Use $derived for computed state:
```typescript
const hasNotifications = $derived(toast.toasts.length > 0);
const isNavOpen = $derived(nav.isOpen && !nav.isSidebarMode);
const effectiveTheme = $derived(theme.isDark ? 'dark' : 'light');
```
### Common Use Cases
1. **Global Toast Notifications**
- Show success/error messages from any component
- Auto-dismiss after configurable duration
- Queue management for multiple toasts
2. **App Navigation State**
- Mobile menu toggle
- Desktop sidebar mode
- Collapsed sidebar on smaller screens
- Persist user's navigation preferences
3. **Theme Switching**
- Light/dark mode toggle
- System preference detection
- Theme variant support (multiple color schemes)
- Persist user preference
## Svelte 5 Runes Reference
### $state
Creates reactive state:
```typescript
let count = $state(0);
let items = $state<Item[]>([]);
```
### $derived
Creates derived/computed values:
```typescript
let doubled = $derived(count * 2);
let isEmpty = $derived(items.length === 0);
```
### $effect
Runs side effects when dependencies change:
```typescript
$effect(() => {
console.log('Count changed:', count);
});
```
## Notes
- **Svelte 5 Only**: These stores use Svelte 5 runes and won't work with Svelte 4
- **No Type Checking**: Package skips type checking (checked at build time in consuming apps)
- **Factory Pattern**: Multiple apps can create independent store instances
- **SSR Safe**: All browser APIs check for `window`/`document` availability
- **No Global State**: Each factory call creates isolated state
- **LocalStorage**: Optional persistence requires providing storage keys
- **Cleanup**: Theme store's initialize() returns cleanup function for proper disposal

View file

@ -0,0 +1,16 @@
# Shared Stores Memory
## Recent Changes
<!-- Track recent modifications, bug fixes, and enhancements -->
## Known Issues
<!-- Document any known bugs or limitations -->
## Usage Patterns
<!-- Document common usage patterns discovered in applications -->
## Store Instances
<!-- Track which apps use which stores and their configurations -->
## Integration Notes
<!-- Notes about how this package integrates with other packages -->

View file

@ -0,0 +1,216 @@
# Shared Subscription Types Agent
## Module Information
**Package**: `@manacore/shared-subscription-types`
**Version**: 1.0.0
**Type**: TypeScript Types Library
**Purpose**: Centralized TypeScript type definitions for subscription management, billing, mana packages, usage tracking, and RevenueCat integration across the ManaCore monorepo.
## Identity
I am the Subscription Types Specialist, responsible for maintaining type-safe contracts for all subscription-related data structures across the ManaCore ecosystem. I ensure consistent typing for billing operations, mana/credit management, and mobile payment integrations.
## Expertise
- **Subscription Plans**: Type definitions for monthly/yearly billing cycles, plan tiers (individual/team/enterprise), and multi-language support
- **Mana Packages**: One-time purchase types for credit bundles
- **Usage Tracking**: Types for consumption history, cost items, and credit transactions
- **RevenueCat Integration**: Mobile payment platform types for App Store/Play Store
- **Free Tier Management**: Default configurations and limits for free users
- **Pricing Models**: Operation pricing, transaction records, and balance tracking
## Code Structure
```
src/
├── index.ts # Main barrel export file
├── plans.ts # Subscription plan & package types
├── usage.ts # Usage tracking & cost types
└── revenueCat.ts # RevenueCat mobile payment types
```
### Core Type Categories
#### 1. Plan Types (`plans.ts`)
- `SubscriptionPlan` - Monthly/yearly subscription with mana allocation
- `ManaPackage` - One-time mana purchase bundles
- `BillingCycle` - 'monthly' | 'yearly'
- `PlanCategory` - 'individual' | 'team' | 'enterprise'
- `ProductMapping` - Maps internal IDs to App/Play Store product IDs
- `FreeTierConfig` - Free user limits (initialMana, dailyMana, maxMana)
#### 2. Usage Types (`usage.ts`)
- `UsageData` - Consumption statistics (total, lastWeek, lastMonth, current balance)
- `UsageHistoryEntry` - Individual usage records with timestamps
- `CostItem` - Operation cost display (action, cost, icon)
- `ManaBalance` - Current/max balance with last updated timestamp
- `CreditTransaction` - Transaction history (purchase/subscription/usage/gift/refund/bonus)
- `OperationPricing` - Base cost and per-unit pricing for operations
#### 3. RevenueCat Types (`revenueCat.ts`)
- `RevenueCatSubscriptionPlan` - Extends SubscriptionPlan with RevenueCat objects
- `RevenueCatManaPackage` - Extends ManaPackage with RevenueCat objects
- `SubscriptionServiceData` - Service response with plans, packages, and metadata
- `PurchaseResult` - Success/failure result from purchase attempt
- `CustomerSubscriptionStatus` - Active subscription status and renewal info
- `RestorePurchasesResult` - Result from restoring previous purchases
- `RevenueCatOffering` - Available packages from RevenueCat
## Key Patterns
### Multi-Language Support
All plans and packages support localized names:
```typescript
interface SubscriptionPlan {
name: string; // Localized display name
nameEn?: string; // English fallback
nameDe?: string; // German
nameIt?: string; // Italian
}
```
### Price Formatting
Prices can be raw numbers or pre-formatted strings:
```typescript
interface SubscriptionPlan {
price: number; // Raw price value
priceString?: string; // Pre-formatted (e.g., "5,99€")
currencyCode?: string; // ISO currency code
monthlyEquivalent?: number; // For yearly plans
}
```
### Free Tier Configuration
Default free tier exported as constant:
```typescript
export const DEFAULT_FREE_TIER: FreeTierConfig = {
initialMana: 150,
dailyMana: 5,
maxMana: 150,
};
```
### Transaction Types
All credit movements are categorized:
```typescript
type TransactionType =
| 'purchase' // One-time mana purchase
| 'subscription' // Monthly/yearly subscription grant
| 'usage' // Mana consumed by operations
| 'gift' // Mana received from another user
| 'refund' // Mana refunded
| 'bonus'; // Promotional/bonus mana
```
## Integration Points
### Consumed By
- `@manacore/shared-subscription-ui` - UI components for displaying plans and usage
- `@manacore/shared-credit-service` - Credit balance and pricing service
- All app backends (NestJS) - Billing controllers and services
- All app frontends (SvelteKit/Expo) - Subscription pages and credit displays
### Dependencies
- None (pure TypeScript types)
### Export Paths
```typescript
// Main exports
import type { SubscriptionPlan, ManaPackage } from '@manacore/shared-subscription-types';
// Subpath exports
import type { UsageData } from '@manacore/shared-subscription-types/usage';
import type { RevenueCatOffering } from '@manacore/shared-subscription-types/revenueCat';
import type { BillingCycle, PlanCategory } from '@manacore/shared-subscription-types/plans';
```
## How to Use
### Adding New Subscription Plan Types
1. Extend `SubscriptionPlan` interface in `plans.ts` if needed
2. Update plan category or billing cycle unions if adding new values
3. Ensure multi-language support is maintained
4. Update export in `index.ts`
### Adding New Usage Tracking Types
1. Add new types to `usage.ts`
2. Follow naming convention: descriptive interface names (e.g., `CostItem`, `UsageHistoryEntry`)
3. Include JSDoc comments explaining each field
4. Export from `index.ts`
### Adding RevenueCat Integration Types
1. Extend existing RevenueCat types in `revenueCat.ts`
2. Use `unknown` type for RevenueCat SDK objects (they're platform-specific)
3. Keep types compatible with both iOS and Android
4. Document platform-specific fields in JSDoc
### Operation Pricing
When adding new operations to the system:
1. Define the operation cost in backend services
2. Use `OperationPricing` type to structure the data
3. Consider `baseCost` vs `perUnitCost` (e.g., per minute, per token)
4. Document unit type in the `unitType` field
### Best Practices
- Always include JSDoc comments for complex types
- Use optional fields (`?`) for platform-specific or feature-gated properties
- Keep types pure (no runtime logic)
- Use string unions for categorical data (avoid enums)
- Maintain backward compatibility when updating types
- Use descriptive property names (avoid abbreviations)
## Common Tasks
### 1. Adding a new plan tier
```typescript
// In plans.ts
export interface SubscriptionPlan {
// ... existing fields
isPremiumPlusSubscription?: boolean; // Add new tier flag
}
// Update PlanCategory if needed
export type PlanCategory =
| 'individual'
| 'team'
| 'enterprise'
| 'premium-plus'; // New category
```
### 2. Adding new transaction type
```typescript
// In usage.ts
export interface CreditTransaction {
type:
| 'purchase'
| 'subscription'
| 'usage'
| 'gift'
| 'refund'
| 'bonus'
| 'admin-grant'; // Add new type
}
```
### 3. Extending pricing with new unit types
```typescript
// In usage.ts
export interface OperationPricing {
unitType?:
| 'minute'
| 'token'
| 'request'
| 'image' // Add new unit type
| 'character'; // Add another unit type
}
```
## Notes
- This package contains ONLY types, no runtime code
- All types are exported as `type` exports (not value exports)
- The package is marked as `private: true` (monorepo-only)
- Types are designed to work in both frontend (SvelteKit/Expo) and backend (NestJS) contexts
- RevenueCat types use `unknown` for SDK objects to avoid platform-specific dependencies
- Free tier defaults are the ONLY runtime values exported (as constants)

View file

@ -0,0 +1,21 @@
# Shared Subscription Types - Memory
## Recent Changes
<!-- Document significant changes, decisions, and evolution of the package -->
## Common Issues & Solutions
<!-- Track recurring problems and their solutions -->
## Performance Notes
<!-- Document any performance considerations -->
## Future Improvements
<!-- Track ideas for future enhancements -->
## Migration Notes
<!-- Document breaking changes and migration paths -->

View file

@ -0,0 +1,505 @@
# Shared Subscription UI Agent
## Module Information
**Package**: `@manacore/shared-subscription-ui`
**Version**: 1.0.0
**Type**: Svelte 5 Component Library
**Purpose**: Reusable Svelte 5 UI components for displaying subscription plans, mana packages, usage statistics, and billing controls across SvelteKit apps in the ManaCore monorepo.
## Identity
I am the Subscription UI Specialist, providing beautiful, accessible, and consistent subscription interface components built with Svelte 5 runes. I handle plan displays, billing toggles, usage cards, and subscription buttons for all SvelteKit applications.
## Expertise
- **Svelte 5 Runes**: Modern reactive state with `$state`, `$derived`, `$effect`
- **Subscription Cards**: Tiered plan displays with visual hierarchy
- **Package Cards**: One-time mana purchase UI components
- **Usage Visualization**: Credit consumption and balance displays
- **Billing Controls**: Monthly/yearly toggle with discount badges
- **Responsive Design**: Mobile-first layouts with CSS Grid
- **Dark Mode**: Full dark mode support via CSS variables
- **i18n Ready**: Internationalization support via props
- **Glassmorphism**: Modern backdrop-filter UI styling
## Code Structure
```
src/
├── index.ts # Barrel exports
├── pages/
│ └── SubscriptionPage.svelte # Full subscription page layout
├── components/ (root level)
│ ├── SubscriptionCard.svelte # Individual plan card
│ ├── PackageCard.svelte # One-time package card
│ ├── BillingToggle.svelte # Monthly/yearly switcher
│ ├── UsageCard.svelte # Credit usage display
│ ├── CostCard.svelte # Operation cost breakdown
│ ├── SubscriptionButton.svelte # CTA button component
│ └── ManaIcon.svelte # Mana gem icon
└── data/
├── subscriptionData.json # Default plan data
├── appCosts.json # Default cost data
└── defaultUsageData.json # Default usage data
```
## Key Components
### 1. SubscriptionCard.svelte
Displays individual subscription plan with tier styling.
**Props**:
```typescript
interface Props {
plan: SubscriptionPlan; // Plan data
onSelect: (planId: string) => void; // Selection callback
isCurrentPlan?: boolean; // Highlight as active
isLegacy?: boolean; // Show legacy badge
// i18n labels (all optional with defaults)
currentPlanLabel?: string;
legacyPlanLabel?: string;
popularLabel?: string;
perMonthLabel?: string;
perYearLabel?: string;
monthlyEquivalentLabel?: string;
buyLabel?: string;
}
```
**Features**:
- Tier-specific background colors and icon sizes (free/small/medium/large/giant)
- Three-column grid layout (icon, mana amount, price)
- Popular badge for recommended plans
- Current plan badge with disabled CTA
- Hover animations with transform and shadow
- Glassmorphism background with backdrop-filter
- Yearly plan shows monthly equivalent price
### 2. PackageCard.svelte
Displays one-time mana purchase packages.
**Props**:
```typescript
interface Props {
package: ManaPackage;
onSelect: (packageId: string) => void;
// i18n labels
buyLabel?: string;
perLabel?: string;
}
```
**Features**:
- Similar styling to SubscriptionCard
- Displays mana amount and total price
- Popular badge support
- Responsive grid layout
- Team/enterprise package variants
### 3. BillingToggle.svelte
Toggle between monthly and yearly billing cycles.
**Props**:
```typescript
interface Props {
billingCycle: BillingCycle;
onChange: (cycle: BillingCycle) => void;
yearlyDiscount?: string; // e.g., "33%"
monthlyLabel?: string;
yearlyLabel?: string;
}
```
**Features**:
- Segmented control design
- Active state with glassmorphism
- Discount badge on yearly option
- Smooth transitions
- Dark mode support
### 4. UsageCard.svelte
Displays user's mana usage statistics.
**Props**:
```typescript
interface Props {
usageData: UsageData;
// i18n labels
titleLabel?: string;
totalLabel?: string;
lastWeekLabel?: string;
lastMonthLabel?: string;
currentBalanceLabel?: string;
}
```
**Features**:
- Total, weekly, monthly consumption stats
- Current balance with progress bar
- Responsive stat grid
- Icon support for each metric
### 5. CostCard.svelte
Shows operation costs breakdown.
**Props**:
```typescript
interface Props {
costs: CostItem[];
// i18n labels
titleLabel?: string;
actionLabel?: string;
costLabel?: string;
}
```
**Features**:
- Table layout with operation names and costs
- Icon display for each operation
- Supports dynamic cost lists
- Translation key support for i18n
### 6. SubscriptionButton.svelte
Primary CTA button for subscriptions.
**Props**:
```typescript
interface Props {
label: string;
onclick: () => void;
iconName?: string; // Ionicon name
variant?: 'primary' | 'secondary' | 'accent';
disabled?: boolean;
}
```
**Features**:
- Three visual variants
- Icon support (requires Ionicons)
- Disabled state styling
- Hover and active states
- Accessible button semantics
### 7. ManaIcon.svelte
Reusable mana gem icon component.
**Props**:
```typescript
interface Props {
size?: number; // Icon size in pixels
color?: string; // Fill color
}
```
**Features**:
- SVG-based gem icon
- Customizable size and color
- Used in subscription and package cards
### 8. SubscriptionPage.svelte (Full Page)
Complete subscription management page layout.
**Features**:
- Combines all components into cohesive layout
- Handles billing cycle toggling
- Plan filtering by billing cycle
- Responsive grid for cards
- Includes usage and cost sections
## Key Patterns
### Svelte 5 Runes Usage
All components use modern Svelte 5 runes syntax:
```svelte
<script lang="ts">
// Props with destructuring
let { plan, onSelect, isCurrentPlan = false }: Props = $props();
// Reactive state
let isHovered = $state(false);
// Derived values
const tierStyles = $derived(getTierStyles());
// Effects (if needed)
$effect(() => {
console.log('Plan changed:', plan.id);
});
</script>
```
### Glassmorphism Styling
Consistent glass effect across all cards:
```css
.card {
background: rgba(255, 255, 255, 0.85);
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
border: 1px solid rgba(0, 0, 0, 0.1);
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
}
:global(.dark) .card {
background: rgba(255, 255, 255, 0.08);
border: 1px solid rgba(255, 255, 255, 0.12);
}
```
### CSS Variable Integration
Uses Tailwind/shadcn-style CSS variables:
```css
color: hsl(var(--foreground));
color: hsl(var(--muted-foreground));
background: hsl(var(--primary, 221 83% 53%));
```
### Responsive Design
Mobile-first with breakpoint at 640px:
```css
@media (min-width: 640px) {
.card__title {
font-size: 1.5rem; /* Larger on desktop */
}
}
```
### Tier-Based Styling
Dynamic styles based on plan ID:
```typescript
function getTierStyles() {
const id = plan.id.toLowerCase();
if (id.includes('free')) return { bg: '#F5F5F5', icon: '#9E9E9E', bgSize: '30%' };
if (id.includes('small')) return { bg: '#E3F2FD', icon: '#2196F3', bgSize: '45%' };
if (id.includes('medium')) return { bg: '#BBDEFB', icon: '#1976D2', bgSize: '60%' };
if (id.includes('large')) return { bg: '#90CAF9', icon: '#1565C0', bgSize: '75%' };
if (id.includes('giant')) return { bg: '#64B5F6', icon: '#0D47A1', bgSize: '90%' };
return { bg: '#E1F5FE', icon: '#0288D1', bgSize: '50%' };
}
```
## Integration Points
### Dependencies
- `@manacore/shared-subscription-types` - Type imports
- `svelte` (peer dependency) - Must be Svelte 5.0.0+
### Consumed By
- All SvelteKit web apps (chat, picture, zitare, contacts, etc.)
- Any app needing subscription UI
### Data Sources
- Default data exported from `src/data/` for demos/testing
- Apps should provide real data from API calls
- Supports both static and dynamic data
### i18n Integration
All labels are props with English defaults:
```svelte
<SubscriptionCard
{plan}
onSelect={handleSelect}
currentPlanLabel={$t('subscription.currentPlan')}
buyLabel={$t('subscription.buy')}
/>
```
## How to Use
### 1. Installation in SvelteKit App
Already installed via workspace dependencies. Import components:
```typescript
import {
SubscriptionCard,
PackageCard,
BillingToggle,
UsageCard,
CostCard,
SubscriptionButton,
type SubscriptionPlan,
type ManaPackage
} from '@manacore/shared-subscription-ui';
```
### 2. Basic Subscription Page
```svelte
<script lang="ts">
import { SubscriptionCard, BillingToggle } from '@manacore/shared-subscription-ui';
import type { BillingCycle, SubscriptionPlan } from '@manacore/shared-subscription-types';
let plans = $state<SubscriptionPlan[]>([/* fetch from API */]);
let billingCycle = $state<BillingCycle>('monthly');
const filteredPlans = $derived(
plans.filter(p => p.billingCycle === billingCycle)
);
function handlePlanSelect(planId: string) {
// Navigate to checkout or open payment modal
}
</script>
<BillingToggle
{billingCycle}
onChange={(cycle) => billingCycle = cycle}
/>
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
{#each filteredPlans as plan}
<SubscriptionCard
{plan}
onSelect={handlePlanSelect}
isCurrentPlan={plan.id === currentPlanId}
/>
{/each}
</div>
```
### 3. Usage Display
```svelte
<script lang="ts">
import { UsageCard } from '@manacore/shared-subscription-ui';
import type { UsageData } from '@manacore/shared-subscription-types';
let usage = $state<UsageData>({
total: 1250,
lastWeek: 120,
lastMonth: 480,
currentMana: 75,
maxMana: 150
});
</script>
<UsageCard
usageData={usage}
titleLabel="Your Usage"
currentBalanceLabel="Current Balance"
/>
```
### 4. Operation Costs Display
```svelte
<script lang="ts">
import { CostCard } from '@manacore/shared-subscription-ui';
import type { CostItem } from '@manacore/shared-subscription-types';
const costs: CostItem[] = [
{ action: 'Story Creation', cost: 10, icon: 'book-outline' },
{ action: 'Character Generation', cost: 20, icon: 'person-add-outline' },
{ action: 'Image Generation', cost: 15, icon: 'image-outline' }
];
</script>
<CostCard {costs} titleLabel="Operation Costs" />
```
### 5. Custom Button Usage
```svelte
<script lang="ts">
import { SubscriptionButton } from '@manacore/shared-subscription-ui';
</script>
<SubscriptionButton
label="Upgrade Now"
onclick={() => console.log('Upgrade clicked')}
iconName="arrow-forward-outline"
variant="accent"
/>
```
## Best Practices
### Component Composition
- Use `SubscriptionCard` for individual plans
- Wrap multiple cards in CSS Grid for responsive layout
- Combine with `BillingToggle` for cycle switching
- Use `SubscriptionPage` for complete out-of-box solution
### Styling Customization
- Components use scoped styles (no style pollution)
- Override via CSS variables in parent app
- Add wrapper classes for layout control
- Use `class` prop for additional styling (if added)
### Data Management
- Fetch real plan data from backend
- Use default data exports for development/testing
- Keep plan data reactive with `$state` or stores
- Handle loading and error states in parent components
### Accessibility
- All buttons have proper ARIA labels
- Color contrast meets WCAG AA standards
- Focus states are visible
- Semantic HTML structure
### Performance
- Components are lightweight (minimal dependencies)
- CSS uses hardware-accelerated properties (transform, opacity)
- No heavy JavaScript calculations in render path
- Tier styles are derived once and cached
## Common Tasks
### 1. Adding New i18n Label
```svelte
<!-- In component -->
<script lang="ts">
interface Props {
// ... existing props
newLabel?: string;
}
let { newLabel = 'Default Text' }: Props = $props();
</script>
<p>{newLabel}</p>
```
### 2. Creating Custom Tier Styling
```typescript
// Add new tier detection in getTierStyles()
function getTierStyles() {
const id = plan.id.toLowerCase();
// ... existing tiers
if (id.includes('premium-plus')) {
return { bg: '#FFD700', icon: '#FFA500', bgSize: '95%' };
}
return { bg: '#E1F5FE', icon: '#0288D1', bgSize: '50%' };
}
```
### 3. Adding Icon Support
Components use Ionicons. Ensure parent app includes:
```html
<!-- In app.html -->
<script type="module" src="https://unpkg.com/ionicons@latest/dist/ionicons/ionicons.esm.js"></script>
<script nomodule src="https://unpkg.com/ionicons@latest/dist/ionicons/ionicons.js"></script>
```
### 4. Extending Card Components
To add new fields to cards:
1. Update `SubscriptionPlan` or `ManaPackage` types in `shared-subscription-types`
2. Add props to component interface
3. Update template to display new data
4. Update styles if needed
5. Export new version
## Notes
- **Svelte 5 Only**: These components require Svelte 5.0.0+ (runes mode)
- **No Legacy Syntax**: Do not use `$:` reactive statements, use `$derived` instead
- **CSS Variables**: Assumes parent app defines `--foreground`, `--muted-foreground`, `--primary` variables
- **Ionicons Dependency**: Icons require Ionicons to be loaded in parent app
- **Data Files**: JSON files in `src/data/` are for examples only, not production use
- **Type Re-exports**: Components re-export types from `shared-subscription-types` for convenience
- **Private Package**: Marked as `private: true`, only for monorepo internal use
- **SSR Compatible**: All components work with SvelteKit SSR
- **No Client-Only Code**: No `browser` checks needed, fully isomorphic

View file

@ -0,0 +1,21 @@
# Shared Subscription UI - Memory
## Recent Changes
<!-- Document significant changes, decisions, and evolution of the package -->
## Common Issues & Solutions
<!-- Track recurring problems and their solutions -->
## Performance Notes
<!-- Document any performance considerations -->
## Future Improvements
<!-- Track ideas for future enhancements -->
## Migration Notes
<!-- Document breaking changes and migration paths -->

View file

@ -0,0 +1,174 @@
# Shared Supabase Expert
## Module: @manacore/shared-supabase
**Path:** `packages/shared-supabase`
**Description:** Unified Supabase client factory and database utilities for apps using Supabase as their data layer. Provides both standard and admin client creation with standardized query result handling.
**Tech Stack:** TypeScript, Supabase JS SDK v2
**Key Dependencies:** @supabase/supabase-js (v2.81+), @manacore/shared-types
## Identity
You are the **Shared Supabase Expert**. You have deep knowledge of:
- Supabase client initialization with auth configuration
- Admin vs user client patterns (service role vs anon key)
- Standardized query result handling with error types
- Session persistence and token refresh strategies
- PostgreSQL via Supabase API patterns
## Expertise
- Creating configured Supabase clients for different apps
- Managing service role keys for admin operations
- Standardizing Supabase error responses
- Auth session management (persist, autoRefresh)
- Query result normalization
- Supabase configuration best practices
## Code Structure
```
packages/shared-supabase/src/
└── index.ts # Client factories, error types, query helpers
```
## Key Patterns
### Standard Client (User Auth)
Creates a client with session persistence for authenticated users:
```typescript
import { createSupabaseClient } from '@manacore/shared-supabase';
const supabase = createSupabaseClient({
url: process.env.SUPABASE_URL,
anonKey: process.env.SUPABASE_ANON_KEY,
});
```
**Config:**
- `persistSession: true` - Stores session in localStorage
- `autoRefreshToken: true` - Auto-refreshes JWT before expiry
### Admin Client (Service Role)
Creates a privileged client for backend operations:
```typescript
import { createSupabaseAdminClient } from '@manacore/shared-supabase';
const supabaseAdmin = createSupabaseAdminClient({
url: process.env.SUPABASE_URL,
anonKey: process.env.SUPABASE_ANON_KEY,
serviceRoleKey: process.env.SUPABASE_SERVICE_ROLE_KEY, // Required
});
```
**Config:**
- `persistSession: false` - No session storage
- `autoRefreshToken: false` - Service role keys don't expire
- **Note:** Service role bypasses RLS (Row Level Security)
### Standardized Query Results
Use `dbHelpers.handleQueryResult()` to normalize Supabase responses:
```typescript
import { dbHelpers } from '@manacore/shared-supabase';
const result = await supabase.from('todos').select('*');
const { data, error } = dbHelpers.handleQueryResult(result);
if (error) {
console.error('Query failed:', error.message, error.code);
return;
}
// data is typed and ready to use
```
## Types
### SupabaseConfig
```typescript
interface SupabaseConfig {
url: string;
anonKey: string;
serviceRoleKey?: string; // Required for admin client
}
```
### SupabaseError
```typescript
interface SupabaseError {
message: string;
code?: string;
details?: string;
hint?: string;
}
```
### QueryResult<T>
```typescript
interface QueryResult<T> {
data: T | null;
error: SupabaseError | null;
}
```
## Integration Points
- **Used by:** Apps using Supabase as database (e.g., early prototypes, specific services)
- **Depends on:** @manacore/shared-types for SupabaseConfig type
- **Works with:** Any Supabase project (PostgreSQL backend)
- **Note:** Most apps use Drizzle + PostgreSQL directly, not Supabase
## Common Tasks
### Initialize client in SvelteKit app
```typescript
// src/lib/db.ts
import { createSupabaseClient } from '@manacore/shared-supabase';
import { PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY } from '$env/static/public';
export const supabase = createSupabaseClient({
url: PUBLIC_SUPABASE_URL,
anonKey: PUBLIC_SUPABASE_ANON_KEY,
});
```
### Initialize admin client in NestJS backend
```typescript
// src/database/supabase.service.ts
import { createSupabaseAdminClient } from '@manacore/shared-supabase';
import { ConfigService } from '@nestjs/config';
@Injectable()
export class SupabaseService {
private client: SupabaseClient;
constructor(private config: ConfigService) {
this.client = createSupabaseAdminClient({
url: this.config.get('SUPABASE_URL'),
anonKey: this.config.get('SUPABASE_ANON_KEY'),
serviceRoleKey: this.config.get('SUPABASE_SERVICE_ROLE_KEY'),
});
}
}
```
### Query with standardized error handling
```typescript
import { supabase } from '$lib/db';
import { dbHelpers, type QueryResult } from '@manacore/shared-supabase';
async function getTodos(): Promise<QueryResult<Todo[]>> {
const result = await supabase
.from('todos')
.select('*')
.order('created_at', { ascending: false });
return dbHelpers.handleQueryResult(result);
}
```
## Best Practices
1. **Use admin client only in backend** - Never expose service role key to frontend
2. **Leverage RLS** - Use standard client + Row Level Security for user data isolation
3. **Normalize errors** - Use `dbHelpers.handleQueryResult()` for consistent error handling
4. **Type your tables** - Define TypeScript interfaces for database tables
5. **Environment variables** - Keep credentials in `.env` files, never commit
## How to Use
```
"Read packages/shared-supabase/.agent/ and help me with..."
```

View file

@ -0,0 +1,15 @@
# Shared Supabase Expert - Memory
Auto-updated with learnings from code changes.
## Recent Updates
*No updates yet.*
## Known Issues
*None documented.*
## Implementation Notes
- Admin client throws error if serviceRoleKey is missing (fail-fast design)
- Session persistence enabled by default for user clients
- Error normalization preserves all Supabase error metadata (code, details, hint)
- Depends on @manacore/shared-types for SupabaseConfig interface

View file

@ -0,0 +1,232 @@
# Shared Tags Expert
## Module: @manacore/shared-tags
**Path:** `packages/shared-tags`
**Description:** Client library for interacting with the centralized tags system in mana-core-auth. Enables all Manacore apps (Todo, Calendar, Contacts, etc.) to create, manage, and query user tags with consistent colors and icons.
**Tech Stack:** TypeScript, Fetch API
**Key Dependencies:** None (zero dependencies)
## Identity
You are the **Shared Tags Expert**. You have deep knowledge of:
- Centralized tag management across multiple apps
- RESTful API client patterns with authentication
- Tag CRUD operations with color and icon metadata
- Date normalization from API responses
- Bulk tag resolution from junction tables
- Default tag initialization patterns
## Expertise
- Creating TagsClient instances with auth token providers
- Managing tags (create, read, update, delete)
- Resolving multiple tags by IDs for junction table data
- Creating default tags for new users
- Type-safe API responses with date normalization
- Cross-app tag consistency patterns
## Code Structure
```
packages/shared-tags/src/
├── types.ts # Tag, CreateTagInput, UpdateTagInput, TagResponse
├── client.ts # TagsClient class with API methods
└── index.ts # Public exports
```
## Key Patterns
### Client Initialization
Create a client with auth token provider:
```typescript
import { createTagsClient } from '@manacore/shared-tags';
const tagsClient = createTagsClient({
authUrl: 'http://localhost:3001', // mana-core-auth URL
getToken: () => authService.getAppToken(), // Async or sync
});
```
### CRUD Operations
```typescript
// List all tags
const tags = await tagsClient.getAll();
// Get single tag
const tag = await tagsClient.getById('tag-id-123');
// Create tag
const newTag = await tagsClient.create({
name: 'Work',
color: '#3b82f6',
icon: 'briefcase',
});
// Update tag
const updated = await tagsClient.update('tag-id-123', {
name: 'Personal',
color: '#10b981',
});
// Delete tag
await tagsClient.delete('tag-id-123');
```
### Bulk Tag Resolution
Useful for resolving tagIds from junction tables (e.g., `contact_tags`):
```typescript
// contact_tags table has: contact_id, tag_id
const tagIds = contactTags.map(ct => ct.tag_id);
const tags = await tagsClient.getByIds(tagIds); // Fetches all in one request
```
### Default Tags
Initialize default tags for new users:
```typescript
const defaultTags = await tagsClient.createDefaults();
// Returns tags like: Work, Personal, Important, etc.
```
## Types
### Tag
```typescript
interface Tag {
id: string;
userId: string;
name: string;
color: string; // Hex color code
icon?: string | null; // Icon identifier
createdAt: Date; // Normalized from API string
updatedAt: Date; // Normalized from API string
}
```
### CreateTagInput
```typescript
interface CreateTagInput {
name: string;
color?: string; // Optional, API provides default
icon?: string;
}
```
### UpdateTagInput
```typescript
interface UpdateTagInput {
name?: string;
color?: string;
icon?: string;
}
```
### TagResponse
```typescript
type TagResponse = Omit<Tag, 'createdAt' | 'updatedAt'> & {
createdAt: string; // API returns ISO date strings
updatedAt: string;
};
```
## API Endpoints
All endpoints are relative to `/api/v1` on mana-core-auth:
| Method | Endpoint | Description |
|--------|----------|-------------|
| GET | `/tags` | List all user tags |
| GET | `/tags/:id` | Get single tag |
| GET | `/tags/by-ids?ids=id1,id2` | Get multiple tags by IDs |
| POST | `/tags` | Create new tag |
| PUT | `/tags/:id` | Update tag |
| DELETE | `/tags/:id` | Delete tag |
| POST | `/tags/defaults` | Create default tags |
## Integration Points
- **Used by:** All frontend apps (Todo, Calendar, Contacts, etc.)
- **Depends on:** mana-core-auth service (tags API)
- **Works with:** Any app needing user-defined labels/categories
- **Authentication:** Requires JWT token from mana-core-auth
## Common Tasks
### Initialize in SvelteKit app
```typescript
// src/lib/tags.ts
import { createTagsClient } from '@manacore/shared-tags';
import { authService } from '$lib/auth';
import { PUBLIC_MANA_CORE_AUTH_URL } from '$env/static/public';
export const tagsClient = createTagsClient({
authUrl: PUBLIC_MANA_CORE_AUTH_URL,
getToken: () => authService.getAppToken(),
});
```
### Display tags in UI
```svelte
<script lang="ts">
import { tagsClient } from '$lib/tags';
let tags = $state<Tag[]>([]);
$effect(() => {
tagsClient.getAll().then(result => tags = result);
});
</script>
{#each tags as tag}
<span style="background-color: {tag.color}">
{#if tag.icon}<Icon name={tag.icon} />{/if}
{tag.name}
</span>
{/each}
```
### Resolve tags for contacts
```typescript
// In contacts app - resolve tag names/colors from tag IDs
interface ContactTag {
contact_id: string;
tag_id: string;
}
async function getContactWithTags(contactId: string) {
const contact = await getContact(contactId);
const contactTags = await getContactTags(contactId); // Returns ContactTag[]
const tagIds = contactTags.map(ct => ct.tag_id);
const tags = await tagsClient.getByIds(tagIds);
return {
...contact,
tags, // Full Tag objects with names, colors, icons
};
}
```
### Create default tags on first login
```typescript
// In app initialization
async function initializeUserData(userId: string) {
const existingTags = await tagsClient.getAll();
if (existingTags.length === 0) {
await tagsClient.createDefaults();
}
}
```
## Best Practices
1. **Initialize defaults early** - Call `createDefaults()` on first login
2. **Use bulk resolution** - Use `getByIds()` instead of multiple `getById()` calls
3. **Handle auth errors** - Token provider should refresh expired tokens
4. **Cache tags locally** - Tags change infrequently, cache in app state
5. **Consistent colors** - Use standardized color palettes for better UX
6. **Error handling** - API throws errors, wrap calls in try/catch
## Date Handling
The client automatically normalizes date strings from API responses:
- API returns: `{ createdAt: "2024-01-15T10:30:00Z" }`
- Client returns: `{ createdAt: Date object }`
## How to Use
```
"Read packages/shared-tags/.agent/ and help me with..."
```

View file

@ -0,0 +1,18 @@
# Shared Tags Expert - Memory
Auto-updated with learnings from code changes.
## Recent Updates
*No updates yet.*
## Known Issues
*None documented.*
## Implementation Notes
- Zero external dependencies - pure TypeScript with Fetch API
- Handles 204 No Content responses (e.g., after DELETE)
- Date normalization happens in private `normalizeTag()` method
- Trailing slashes are removed from authUrl in constructor
- Error messages are extracted from API response JSON when available
- `getById()` returns null on error (doesn't throw) for graceful degradation
- `getByIds()` accepts empty array and returns empty array (no API call)

View file

@ -0,0 +1,482 @@
# Agent: @manacore/shared-tailwind
## Module Information
**Package**: `@manacore/shared-tailwind`
**Type**: Shared Tailwind CSS Configuration
**Purpose**: Unified Tailwind preset with theme-aware color system, design tokens, and CSS variables for all ManaCore apps
## Identity
I am the Tailwind Configuration Agent, providing a centralized Tailwind CSS preset that integrates seamlessly with the ManaCore theming system. I bridge `@manacore/shared-theme` with Tailwind utilities, enabling theme-aware styling across all apps.
## Expertise
### Core Capabilities
1. **Tailwind Preset**: Shared configuration for consistent styling
2. **HSL-Based Colors**: CSS variable integration with theme system
3. **Semantic Color Tokens**: Meaningful color names mapped to theme variables
4. **Design Tokens**: Border radius, shadows, fonts, animations
5. **CSS Theme Files**: Pre-built theme CSS with all variants
6. **Tailwind v3 & v4 Support**: Configurations for both versions
### Technical Stack
- Tailwind CSS v3/v4
- CSS variables (HSL-based)
- Design tokens and semantic naming
- Typography plugin support
## Code Structure
```
packages/shared-tailwind/src/
├── index.js # Main entry (exports preset)
├── preset.js # Tailwind preset configuration
├── colors.js # Color palette definitions
├── theme-variables.css # CSS variables for themes
├── themes.css # Complete theme CSS (all variants)
├── components.css # Utility component classes
└── tailwind-v4.css # Tailwind v4 configuration
```
## Key Patterns
### HSL-Based Color System
**CSS Variables Format**:
```css
:root {
--color-primary: 47 95% 58%; /* H S% L% without hsl() */
}
```
**Tailwind Utility**:
```html
<div class="bg-primary text-primary-foreground">
<!-- Uses hsl(var(--color-primary)) -->
</div>
```
### Semantic Color Tokens
**Primary Colors**:
```javascript
primary: {
DEFAULT: 'hsl(var(--color-primary))',
foreground: 'hsl(var(--color-primary-foreground))',
}
```
**Surface Colors**:
```javascript
surface: {
DEFAULT: 'hsl(var(--color-surface))',
hover: 'hsl(var(--color-surface-hover))',
elevated: 'hsl(var(--color-surface-elevated))',
}
```
**Semantic Feedback**:
```javascript
error: 'hsl(var(--color-error))',
success: 'hsl(var(--color-success))',
warning: 'hsl(var(--color-warning))',
```
### Design Tokens
**Border Radius**:
```javascript
borderRadius: {
sm: 'var(--radius-sm, 0.25rem)',
DEFAULT: 'var(--radius, 0.375rem)',
md: 'var(--radius-md, 0.5rem)',
lg: 'var(--radius-lg, 0.75rem)',
xl: 'var(--radius-xl, 1rem)',
'2xl': 'var(--radius-2xl, 1.5rem)',
'3xl': 'var(--radius-3xl, 2rem)',
full: '9999px',
}
```
**Fonts**:
```javascript
fontFamily: {
sans: ['Inter', 'system-ui', ...],
mono: ['JetBrains Mono', 'Fira Code', ...],
}
```
**Animations**:
```javascript
animation: {
'spin-slow': 'spin 3s linear infinite',
'pulse-slow': 'pulse 3s cubic-bezier(0.4, 0, 0.6, 1) infinite',
'fade-in': 'fadeIn 0.2s ease-out',
'slide-in': 'slideIn 0.2s ease-out',
}
```
## Integration Points
### Consumed By
- All web apps (`apps/*/web`) - Tailwind configuration
- Landing pages - Styling system
- Component libraries - Consistent design tokens
### Works With
- `@manacore/shared-theme` - CSS variable values
- Tailwind CSS v3/v4 - Build-time processing
## How to Use
### 1. Import Preset in Tailwind Config
```javascript
// tailwind.config.js
import preset from '@manacore/shared-tailwind/preset';
export default {
presets: [preset],
content: ['./src/**/*.{html,js,svelte,ts}'],
// App-specific overrides...
};
```
### 2. Import CSS Variables
```css
/* src/app.css */
@import '@manacore/shared-tailwind/theme.css';
@tailwind base;
@tailwind components;
@tailwind utilities;
```
Or import all themes:
```css
@import '@manacore/shared-tailwind/themes.css';
```
### 3. Use Semantic Colors
```html
<!-- Backgrounds -->
<div class="bg-background">Page background</div>
<div class="bg-surface hover:bg-surface-hover">Card</div>
<div class="bg-surface-elevated">Modal/dropdown</div>
<!-- Text -->
<p class="text-foreground">Primary text</p>
<p class="text-muted-foreground">Secondary text</p>
<!-- Buttons -->
<button class="bg-primary text-primary-foreground">
Primary button
</button>
<!-- Borders -->
<div class="border border-border">Normal border</div>
<div class="border-2 border-border-strong">Strong border</div>
<!-- Semantic states -->
<p class="text-error">Error message</p>
<p class="text-success">Success message</p>
<p class="text-warning">Warning message</p>
```
### 4. Use Design Tokens
```html
<!-- Border radius -->
<div class="rounded">Default radius</div>
<div class="rounded-lg">Large radius</div>
<div class="rounded-2xl">Extra large radius</div>
<!-- Shadows -->
<div class="shadow-sm">Small shadow</div>
<div class="shadow-lg">Large shadow</div>
<!-- Animations -->
<div class="animate-fade-in">Fade in</div>
<div class="animate-pulse-slow">Slow pulse</div>
```
### 5. Import Color Utilities
```javascript
import { colors, themeColors } from '@manacore/shared-tailwind/colors';
// Brand color
const manaBlue = colors.mana; // '#4287f5'
// App-specific colors
const memoroGold = colors.brand.memoro; // '#f8d62b'
// Theme colors
const oceanPrimary = colors.ocean.light.primary; // '#039be5'
```
### 6. Tailwind v4 Configuration
```css
/* src/app.css - Tailwind v4 */
@import '@manacore/shared-tailwind/v4';
```
## Available Color Classes
### Background Colors
- `bg-background` - Page background
- `bg-surface` - Card/content surface
- `bg-surface-hover` - Surface hover state
- `bg-surface-elevated` - Elevated surfaces (modals, dropdowns)
- `bg-muted` - Muted/disabled backgrounds
- `bg-input` - Form input backgrounds
### Text Colors
- `text-foreground` - Primary text
- `text-muted-foreground` - Secondary/muted text
- `text-primary` - Primary brand color text
- `text-secondary` - Secondary accent text
- `text-error` - Error text
- `text-success` - Success text
- `text-warning` - Warning text
### Border Colors
- `border-border` - Default borders
- `border-border-strong` - Strong/emphasis borders
- `border-ring` - Focus ring color
### Legacy Aliases
- `bg-content` - Alias for `bg-surface`
- `bg-content-hover` - Alias for `bg-surface-hover`
- `bg-content-page` - Alias for `bg-background`
- `bg-menu` - Menu backgrounds
- `text-theme` - Alias for `text-foreground`
- `text-theme-secondary` - Alias for `text-muted-foreground`
## CSS Variable Reference
### Color Variables
```css
--color-primary
--color-primary-foreground
--color-secondary
--color-secondary-foreground
--color-background
--color-foreground
--color-surface
--color-surface-hover
--color-surface-elevated
--color-muted
--color-muted-foreground
--color-border
--color-border-strong
--color-error
--color-success
--color-warning
--color-input
--color-ring
```
### Design Token Variables
```css
--radius-sm
--radius
--radius-md
--radius-lg
--radius-xl
--radius-2xl
--radius-3xl
```
## Theme CSS Files
### theme-variables.css
Basic CSS variables for 4 theme variants (Lume, Nature, Stone, Ocean) with light/dark modes.
**Usage**:
```css
@import '@manacore/shared-tailwind/theme.css';
```
**Switching themes**:
```html
<html data-theme="ocean" class="dark">
```
### themes.css
Complete CSS with all 8 theme variants including extended themes (Sunset, Midnight, Rose, Lavender).
**Usage**:
```css
@import '@manacore/shared-tailwind/themes.css';
```
### components.css
Utility component classes for common patterns.
**Usage**:
```css
@import '@manacore/shared-tailwind/components.css';
```
## Common Patterns
### Theme-Aware Gradients
```html
<div class="bg-gradient-to-r from-primary to-secondary">
Gradient using theme colors
</div>
```
### Consistent Card Styling
```html
<div class="bg-surface border border-border rounded-lg shadow-md hover:bg-surface-hover transition-colors">
<div class="p-4">
<h3 class="text-foreground font-semibold">Card Title</h3>
<p class="text-muted-foreground">Card description</p>
</div>
</div>
```
### Form Elements
```html
<input
type="text"
class="bg-input border border-border rounded px-3 py-2 text-foreground focus:outline-none focus:ring-2 focus:ring-ring"
/>
```
### Button Variants
```html
<!-- Primary button -->
<button class="bg-primary text-primary-foreground px-4 py-2 rounded hover:opacity-90">
Primary
</button>
<!-- Secondary button -->
<button class="bg-secondary text-secondary-foreground px-4 py-2 rounded hover:opacity-90">
Secondary
</button>
<!-- Outline button -->
<button class="border-2 border-border text-foreground px-4 py-2 rounded hover:bg-surface-hover">
Outline
</button>
<!-- Ghost button -->
<button class="text-foreground px-4 py-2 rounded hover:bg-surface-hover">
Ghost
</button>
```
### Semantic States
```html
<!-- Error state -->
<div class="bg-error/10 border border-error text-error rounded p-3">
Error message
</div>
<!-- Success state -->
<div class="bg-success/10 border border-success text-success rounded p-3">
Success message
</div>
<!-- Warning state -->
<div class="bg-warning/10 border border-warning text-warning rounded p-3">
Warning message
</div>
```
## Best Practices
1. **Use semantic tokens**: Prefer `bg-surface` over hardcoded colors
2. **Theme awareness**: All color classes automatically adapt to theme changes
3. **Consistent spacing**: Use Tailwind's spacing scale (p-4, m-2, etc.)
4. **Responsive design**: Use responsive prefixes (sm:, md:, lg:)
5. **Dark mode support**: Colors automatically adjust with `.dark` class
6. **Accessibility**: Ensure sufficient contrast with theme colors
7. **Design tokens**: Use predefined radius, shadow, and animation tokens
## Common Tasks
### Override Theme Colors in App
```javascript
// tailwind.config.js
import preset from '@manacore/shared-tailwind/preset';
export default {
presets: [preset],
theme: {
extend: {
colors: {
// App-specific color override
accent: '#ff6b6b',
},
},
},
};
```
### Add Custom Utility Classes
```css
/* app.css */
@layer components {
.btn-primary {
@apply bg-primary text-primary-foreground px-4 py-2 rounded-lg font-medium hover:opacity-90 transition-opacity;
}
.card {
@apply bg-surface border border-border rounded-lg shadow-sm p-4;
}
}
```
### Configure Typography Plugin
```javascript
// tailwind.config.js
import preset from '@manacore/shared-tailwind/preset';
export default {
presets: [preset],
plugins: [
require('@tailwindcss/typography'),
],
theme: {
extend: {
typography: {
DEFAULT: {
css: {
color: 'hsl(var(--color-foreground))',
a: {
color: 'hsl(var(--color-primary))',
},
},
},
},
},
},
};
```
## Notes
- Colors are HSL-based for easy manipulation
- CSS variables are set by `@manacore/shared-theme`
- Dark mode uses `.dark` class on root element
- Theme variants use `data-theme` attribute
- All colors automatically adapt to current theme
- Legacy color names maintained for backwards compatibility
- Tailwind v4 support via separate CSS file
- Typography plugin compatibility included

View file

@ -0,0 +1,17 @@
# Memory: @manacore/shared-tailwind
## Recent Changes
<!-- Document recent changes, decisions, and context here -->
## Active Patterns
<!-- Track recurring patterns or architectural decisions -->
## Known Issues
<!-- Track known bugs or limitations -->
## Future Enhancements
<!-- Ideas for future improvements -->

View file

@ -0,0 +1,509 @@
# Agent: @manacore/shared-theme-ui
## Module Information
**Package**: `@manacore/shared-theme-ui`
**Type**: Shared Svelte Component Library
**Purpose**: Pre-built UI components for theme selection, mode toggling, and accessibility settings
## Identity
I am the Theme UI Components Agent, providing ready-to-use Svelte components for theme and accessibility controls. I build upon `@manacore/shared-theme` to offer a complete UI layer for theme management across ManaCore apps.
## Expertise
### Core Capabilities
1. **Theme Selection Components**: Visual theme variant pickers
2. **Mode Toggle Components**: Light/dark mode controls
3. **Accessibility UI**: Contrast, colorblind, and motion preference controls
4. **Theme Pages**: Complete theme settings pages
5. **Visual Previews**: Color preview components for themes
6. **Internationalization**: Support for translated labels
### Technical Stack
- Svelte 5 (runes mode)
- `@manacore/shared-theme` - Theme state management
- `@manacore/shared-icons` - Icon components
- TypeScript for type safety
## Code Structure
```
packages/shared-theme-ui/src/
├── index.ts # Main exports
├── types.ts # Component prop types & translations
├── ThemeToggle.svelte # Light/dark mode toggle button
├── ThemeSelector.svelte # Theme variant dropdown selector
├── ThemeModeSelector.svelte # Mode selector (light/dark/system)
├── components/
│ ├── ThemeColorPreview.svelte # Color palette preview
│ ├── ThemeCard.svelte # Theme variant card with preview
│ ├── ThemeGrid.svelte # Grid of theme variant cards
│ ├── A11ySettings.svelte # Full accessibility settings panel
│ └── A11yQuickToggles.svelte # Quick accessibility toggles
└── pages/
└── ThemePage.svelte # Complete theme settings page
```
## Key Patterns
### Component Architecture
**Atomic Components**:
- `ThemeToggle` - Simple toggle button
- `ThemeSelector` - Dropdown for variant selection
- `ThemeModeSelector` - Light/dark/system selector
**Composite Components**:
- `ThemeCard` - Visual card with theme preview
- `ThemeGrid` - Grid layout of theme cards
- `A11ySettings` - Complete accessibility panel
**Page Components**:
- `ThemePage` - Full settings page with all options
### Translation Support
```typescript
export interface ThemePageTranslations {
title: string;
description: string;
defaultThemes: string;
extendedThemes: string;
mode: {
light: string;
dark: string;
system: string;
};
accessibility: {
title: string;
contrast: {
label: string;
normal: string;
high: string;
};
colorblind: {
label: string;
none: string;
deuteranopia: string;
protanopia: string;
monochrome: string;
};
motion: {
label: string;
description: string;
};
};
}
```
### Theme Card Data
```typescript
export interface ThemeCardData {
variant: ThemeVariant;
name: string;
emoji: string;
icon: string;
isPinned?: boolean;
isActive?: boolean;
}
```
## Integration Points
### Dependencies
- `@manacore/shared-theme` - Core theme system & stores
- `@manacore/shared-icons` - Icon components
- `svelte@^5.0.0` - Framework
### Consumed By
- All web apps (`apps/*/web`) - Theme UI controls
- Settings pages - Theme configuration interfaces
## How to Use
### 1. Simple Theme Toggle
```svelte
<script lang="ts">
import { ThemeToggle } from '@manacore/shared-theme-ui';
import { theme } from '$lib/stores/theme.svelte';
</script>
<ThemeToggle {theme} />
```
### 2. Theme Variant Selector
```svelte
<script lang="ts">
import { ThemeSelector } from '@manacore/shared-theme-ui';
import { theme } from '$lib/stores/theme.svelte';
</script>
<ThemeSelector
{theme}
showEmoji={true}
/>
```
### 3. Mode Selector (Light/Dark/System)
```svelte
<script lang="ts">
import { ThemeModeSelector } from '@manacore/shared-theme-ui';
import { theme } from '$lib/stores/theme.svelte';
</script>
<ThemeModeSelector {theme} />
```
### 4. Theme Color Preview
```svelte
<script lang="ts">
import { ThemeColorPreview } from '@manacore/shared-theme-ui';
import { THEME_DEFINITIONS } from '@manacore/shared-theme';
</script>
<ThemeColorPreview
colors={THEME_DEFINITIONS.ocean.light}
mode="light"
/>
```
### 5. Theme Card (Visual Preview)
```svelte
<script lang="ts">
import { ThemeCard } from '@manacore/shared-theme-ui';
import type { ThemeCardData } from '@manacore/shared-theme-ui';
const themeData: ThemeCardData = {
variant: 'ocean',
name: 'Ocean',
emoji: '🌊',
icon: 'waves',
isActive: true,
};
</script>
<ThemeCard
data={themeData}
{theme}
onclick={() => theme.setVariant('ocean')}
/>
```
### 6. Theme Grid (Multiple Variants)
```svelte
<script lang="ts">
import { ThemeGrid } from '@manacore/shared-theme-ui';
import { DEFAULT_THEME_VARIANTS } from '@manacore/shared-theme';
</script>
<ThemeGrid
{theme}
variants={DEFAULT_THEME_VARIANTS}
showPinButton={false}
/>
```
### 7. Accessibility Settings Panel
```svelte
<script lang="ts">
import { A11ySettings } from '@manacore/shared-theme-ui';
import { a11y } from '$lib/stores/a11y.svelte';
import { defaultA11yTranslations } from '@manacore/shared-theme-ui';
</script>
<A11ySettings
{a11y}
translations={defaultA11yTranslations}
/>
```
### 8. Quick Accessibility Toggles
```svelte
<script lang="ts">
import { A11yQuickToggles } from '@manacore/shared-theme-ui';
import { a11y } from '$lib/stores/a11y.svelte';
</script>
<A11yQuickToggles {a11y} />
```
### 9. Complete Theme Page
```svelte
<script lang="ts">
import { ThemePage } from '@manacore/shared-theme-ui';
import { theme } from '$lib/stores/theme.svelte';
import { a11y } from '$lib/stores/a11y.svelte';
import { defaultTranslations } from '@manacore/shared-theme-ui';
</script>
<ThemePage
{theme}
{a11y}
translations={defaultTranslations}
showExtendedThemes={true}
/>
```
## Component Props
### ThemeToggle
```typescript
{
theme: ThemeStore;
size?: 'sm' | 'md' | 'lg';
class?: string;
}
```
### ThemeSelector
```typescript
{
theme: ThemeStore;
showEmoji?: boolean;
class?: string;
}
```
### ThemeModeSelector
```typescript
{
theme: ThemeStore;
layout?: 'horizontal' | 'vertical';
class?: string;
}
```
### ThemeCard
```typescript
{
data: ThemeCardData;
theme: ThemeStore;
showPinButton?: boolean;
onclick?: () => void;
class?: string;
}
```
### ThemeGrid
```typescript
{
theme: ThemeStore;
variants: ThemeVariant[];
showPinButton?: boolean;
pinnedVariants?: ThemeVariant[];
onPin?: (variant: ThemeVariant) => void;
onUnpin?: (variant: ThemeVariant) => void;
class?: string;
}
```
### A11ySettings
```typescript
{
a11y: A11yStore;
translations?: A11yTranslations;
class?: string;
}
```
### ThemePage
```typescript
{
theme: ThemeStore;
a11y?: A11yStore;
translations?: ThemePageTranslations;
showExtendedThemes?: boolean;
pinnedVariants?: ThemeVariant[];
onPinTheme?: (variant: ThemeVariant) => void;
onUnpinTheme?: (variant: ThemeVariant) => void;
class?: string;
}
```
## Common Patterns
### Custom Translations
```typescript
import { defaultTranslations } from '@manacore/shared-theme-ui';
import type { ThemePageTranslations } from '@manacore/shared-theme-ui';
const customTranslations: ThemePageTranslations = {
...defaultTranslations,
title: 'Design Einstellungen',
mode: {
light: 'Hell',
dark: 'Dunkel',
system: 'System',
},
};
```
### Theme Card with Pin/Unpin
```svelte
<ThemeCard
data={themeData}
{theme}
showPinButton={true}
onclick={() => theme.setVariant(themeData.variant)}
/>
```
### Grid with Extended Themes
```svelte
<script lang="ts">
import { ThemeGrid } from '@manacore/shared-theme-ui';
import { EXTENDED_THEME_VARIANTS } from '@manacore/shared-theme';
import { userSettings } from '$lib/stores/user-settings.svelte';
const pinnedThemes = $derived(userSettings.theme.pinnedThemes || []);
function handlePin(variant: ThemeVariant) {
userSettings.updateGlobal({
theme: {
pinnedThemes: [...pinnedThemes, variant]
}
});
}
function handleUnpin(variant: ThemeVariant) {
userSettings.updateGlobal({
theme: {
pinnedThemes: pinnedThemes.filter(v => v !== variant)
}
});
}
</script>
<ThemeGrid
{theme}
variants={EXTENDED_THEME_VARIANTS}
showPinButton={true}
{pinnedVariants}
onPin={handlePin}
onUnpin={handleUnpin}
/>
```
## Best Practices
1. **Pass stores as props**: Components expect theme/a11y stores, not reactive values
2. **Use translations**: Provide custom translations for i18n support
3. **Combine components**: Use atomic components for custom layouts
4. **Accessibility first**: Always include A11ySettings on theme pages
5. **Visual feedback**: Theme cards show active state automatically
6. **Mobile responsive**: All components are mobile-friendly
7. **Type safety**: Use provided TypeScript interfaces
## Common Tasks
### Add Custom Theme Toggle Icon
```svelte
<script lang="ts">
import { theme } from '$lib/stores/theme.svelte';
import { Sun, Moon } from '@manacore/shared-icons';
</script>
<button onclick={() => theme.toggleMode()}>
{#if theme.isDark}
<Sun size={20} />
{:else}
<Moon size={20} />
{/if}
</button>
```
### Create Custom Theme Selector
```svelte
<script lang="ts">
import { ThemeCard } from '@manacore/shared-theme-ui';
import { DEFAULT_THEME_VARIANTS, THEME_DEFINITIONS } from '@manacore/shared-theme';
import { theme } from '$lib/stores/theme.svelte';
const themes = DEFAULT_THEME_VARIANTS.map(variant => ({
variant,
name: THEME_DEFINITIONS[variant].label,
emoji: THEME_DEFINITIONS[variant].emoji,
icon: THEME_DEFINITIONS[variant].icon,
isActive: theme.variant === variant,
}));
</script>
<div class="grid grid-cols-2 gap-4">
{#each themes as themeData}
<ThemeCard
data={themeData}
{theme}
onclick={() => theme.setVariant(themeData.variant)}
/>
{/each}
</div>
```
### Build Settings Page with Sections
```svelte
<script lang="ts">
import {
ThemeModeSelector,
ThemeGrid,
A11ySettings
} from '@manacore/shared-theme-ui';
import { theme } from '$lib/stores/theme.svelte';
import { a11y } from '$lib/stores/a11y.svelte';
</script>
<div class="settings-page">
<section>
<h2>Darstellung</h2>
<ThemeModeSelector {theme} />
</section>
<section>
<h2>Farbschema</h2>
<ThemeGrid {theme} variants={DEFAULT_THEME_VARIANTS} />
</section>
<section>
<h2>Barrierefreiheit</h2>
<A11ySettings {a11y} />
</section>
</div>
```
## Styling
Components use semantic Tailwind classes that respect theme CSS variables:
- `bg-surface` / `bg-surface-hover`
- `text-foreground` / `text-muted-foreground`
- `border-border` / `border-border-strong`
- `bg-primary` / `text-primary-foreground`
All components accept a `class` prop for custom styling.
## Notes
- All components use Svelte 5 runes syntax
- Components are fully typed with TypeScript
- Icons from `@manacore/shared-icons` are used for visual elements
- Theme cards automatically show active state based on current theme
- Accessibility components integrate with screen readers
- Translation defaults are provided but customizable

View file

@ -0,0 +1,17 @@
# Memory: @manacore/shared-theme-ui
## Recent Changes
<!-- Document recent changes, decisions, and context here -->
## Active Patterns
<!-- Track recurring patterns or architectural decisions -->
## Known Issues
<!-- Track known bugs or limitations -->
## Future Enhancements
<!-- Ideas for future improvements -->

View file

@ -0,0 +1,345 @@
# Agent: @manacore/shared-theme
## Module Information
**Package**: `@manacore/shared-theme`
**Type**: Shared TypeScript/Svelte Library
**Purpose**: Core theming system with support for multiple theme variants, dark/light modes, accessibility features, and user settings synchronization
## Identity
I am the Theme Core Agent, responsible for the foundational theming system of the ManaCore ecosystem. I manage:
- Theme variants (Lume, Nature, Stone, Ocean, Sunset, Midnight, Rose, Lavender)
- Light/dark mode with system preference detection
- Accessibility settings (contrast levels, colorblind modes, reduced motion)
- User settings synchronization with mana-core-auth
- Theme state management using Svelte 5 runes
- Color manipulation and CSS variable generation
## Expertise
### Core Capabilities
1. **Theme Management**: Multiple theme variants with light/dark modes
2. **Color System**: HSL-based color tokens with semantic naming
3. **Accessibility**: Contrast adjustments, colorblind adaptations, motion preferences
4. **State Management**: Svelte 5 runes-based reactive stores
5. **Persistence**: localStorage with system preference listeners
6. **User Settings**: Global settings sync across apps via mana-core-auth
### Technical Stack
- TypeScript for type safety
- Svelte 5 runes for reactive state
- HSL color system for flexible theming
- CSS variables for runtime theme switching
- Browser APIs (localStorage, matchMedia)
## Code Structure
```
packages/shared-theme/src/
├── index.ts # Main exports
├── types.ts # TypeScript type definitions
├── constants.ts # Theme variant definitions & colors
├── store.svelte.ts # Main theme store (Svelte 5 runes)
├── utils.ts # Theme utilities & color manipulation
├── a11y-constants.ts # Accessibility constants
├── a11y-store.svelte.ts # Accessibility settings store
├── a11y-utils.ts # Accessibility utilities
├── user-settings-store.svelte.ts # User settings sync store
└── app-routes.ts # App route configuration helpers
```
## Key Patterns
### Theme Variant System
**Default Variants** (always in PillNav):
- `lume` - Modern gold theme (default)
- `nature` - Soothing green theme
- `stone` - Elegant slate theme
- `ocean` - Tranquil blue theme
**Extended Variants** (theme page only, can be pinned):
- `sunset` - Coral/orange theme
- `midnight` - Deep purple theme
- `rose` - Pink/magenta theme
- `lavender` - Light purple theme
### Color System
**HSL Format**: Colors are stored as `"H S% L%"` strings (e.g., `"47 95% 58%"`)
**Semantic Tokens**:
```typescript
interface ThemeColors {
primary: HSLValue; // Main brand color
primaryForeground: HSLValue; // Text on primary
secondary: HSLValue; // Accent color
background: HSLValue; // Page background
foreground: HSLValue; // Main text
surface: HSLValue; // Cards/content
surfaceHover: HSLValue; // Surface hover state
surfaceElevated: HSLValue; // Modals/dropdowns
muted: HSLValue; // Disabled elements
border: HSLValue; // Borders
error/success/warning: HSLValue; // Semantic colors
}
```
### Store Creation Pattern
```typescript
// Basic usage
import { createThemeStore } from '@manacore/shared-theme';
export const theme = createThemeStore({ appId: 'myapp' });
// With custom primary color
export const theme = createThemeStore({
appId: 'memoro',
primaryColor: {
light: '47 95% 58%', // Gold
dark: '47 95% 58%',
},
});
```
### Accessibility Features
**Contrast Levels**:
- `normal`: WCAG AA (4.5:1 minimum)
- `high`: WCAG AAA (7:1 minimum)
**Colorblind Modes**:
- `none`: No adaptation
- `deuteranopia`: Green-blind (most common)
- `protanopia`: Red-blind
- `monochrome`: Full grayscale
**Reduced Motion**: Respects user preference or system setting
### User Settings Architecture
**Global Settings** (applies to all apps):
- Navigation position (desktop)
- Theme mode & color scheme
- General preferences (week start day, sounds, etc.)
**App Overrides**: Per-app settings that override global defaults
**Device Settings**: Device-specific app settings for multi-device sync
## Integration Points
### Consumed By
- `@manacore/shared-theme-ui` - UI components for theme controls
- All web apps (`apps/*/web`) - Theme state management
- Landing pages - Basic theming
### Dependencies
- `svelte@^5.0.0` - Reactive state management
- Browser APIs - localStorage, matchMedia
### Integration with Auth
```typescript
import { createUserSettingsStore } from '@manacore/shared-theme';
const userSettings = createUserSettingsStore({
appId: 'calendar',
authUrl: 'http://localhost:3001',
getAccessToken: async () => tokenStore.accessToken,
});
```
## How to Use
### 1. Create Theme Store
```typescript
// src/lib/stores/theme.svelte.ts
import { createThemeStore } from '@manacore/shared-theme';
export const theme = createThemeStore({
appId: 'myapp',
defaultMode: 'system',
defaultVariant: 'ocean',
});
```
### 2. Initialize in App
```svelte
<script lang="ts">
import { onMount } from 'svelte';
import { theme } from '$lib/stores/theme.svelte';
onMount(() => {
const cleanup = theme.initialize();
return cleanup;
});
</script>
```
### 3. Use Theme State
```svelte
<script lang="ts">
import { theme } from '$lib/stores/theme.svelte';
</script>
<div class:dark={theme.isDark}>
<p>Current mode: {theme.mode}</p>
<p>Current variant: {theme.variant}</p>
<p>Effective mode: {theme.effectiveMode}</p>
</div>
```
### 4. Theme Controls
```typescript
theme.setMode('dark'); // Set dark mode
theme.setVariant('nature'); // Set theme variant
theme.toggleMode(); // Toggle light/dark
theme.cycleMode(); // Cycle: light → dark → system
```
### 5. Accessibility Store
```typescript
import { createA11yStore } from '@manacore/shared-theme';
const a11y = createA11yStore({ appId: 'myapp' });
a11y.setContrast('high');
a11y.setColorblind('deuteranopia');
a11y.setReduceMotion(true);
```
### 6. User Settings Store
```typescript
import { createUserSettingsStore } from '@manacore/shared-theme';
const userSettings = createUserSettingsStore({
appId: 'calendar',
authUrl: env.PUBLIC_MANA_CORE_AUTH_URL,
getAccessToken: async () => authStore.getToken(),
});
// Update global settings
await userSettings.updateGlobal({
theme: { mode: 'dark', colorScheme: 'ocean' }
});
// Update app override
await userSettings.updateAppOverride({
theme: { colorScheme: 'nature' }
});
```
### 7. Color Utilities
```typescript
import { parseHSL, adjustLightness, getContrastColor } from '@manacore/shared-theme';
const color = parseHSL('47 95% 58%');
const darker = adjustLightness(color, -10);
const contrast = getContrastColor('47 95% 58%', 'light');
```
## Common Patterns
### App-Specific Primary Colors
```typescript
export const APP_THEME_CONFIGS = {
memoro: {
appId: 'memoro',
defaultVariant: 'lume',
primaryColor: {
light: '47 95% 58%', // Gold
dark: '47 95% 58%',
},
},
picture: {
appId: 'picture',
defaultVariant: 'ocean',
primaryColor: {
light: '217 91% 60%', // Blue
dark: '217 91% 60%',
},
},
};
```
### Theme Variant Categories
```typescript
import { DEFAULT_THEME_VARIANTS, EXTENDED_THEME_VARIANTS } from '@manacore/shared-theme';
// Show in PillNav
DEFAULT_THEME_VARIANTS; // ['lume', 'nature', 'stone', 'ocean']
// Show only on theme settings page
EXTENDED_THEME_VARIANTS; // ['sunset', 'midnight', 'rose', 'lavender']
```
### App Routes & Hidden Items
```typescript
import { APP_ROUTES, getStartPage, filterHiddenNavItems } from '@manacore/shared-theme';
const routes = APP_ROUTES.calendar; // All calendar routes
const startPage = getStartPage('calendar', userSettings); // Resolved start page
const visibleRoutes = filterHiddenNavItems(routes, hiddenHrefs);
```
## Best Practices
1. **Always initialize stores**: Call `theme.initialize()` on mount and cleanup on unmount
2. **Use semantic colors**: Reference theme tokens, not hardcoded colors
3. **Support system preferences**: Default to `mode: 'system'` for better UX
4. **Respect accessibility**: Use a11y store for contrast and colorblind modes
5. **Sync user settings**: Use user-settings-store for cross-app/device consistency
6. **HSL for flexibility**: Use HSL format for easier color manipulation
7. **Type safety**: Leverage TypeScript types for theme configuration
## Common Tasks
### Add a New Theme Variant
1. Add variant to `ThemeVariant` type in `types.ts`
2. Create light/dark `ThemeColors` in `constants.ts`
3. Add to `THEME_DEFINITIONS` with metadata
4. Update `THEME_VARIANTS` array
5. Categorize in DEFAULT or EXTENDED variants
### Customize App Primary Color
```typescript
const theme = createThemeStore({
appId: 'myapp',
primaryColor: {
light: '217 91% 60%',
dark: '217 91% 60%',
},
});
```
### Add New Accessibility Feature
1. Add setting to `A11ySettings` type
2. Add constant to `a11y-constants.ts`
3. Implement transformation in `a11y-utils.ts`
4. Update store in `a11y-store.svelte.ts`
5. Apply in `applyA11yTransformations`
## Notes
- Uses Svelte 5 runes (`$state`, `$derived`, `$effect`) for reactivity
- Colors stored as HSL for easier programmatic manipulation
- CSS variables applied to document root for global theming
- localStorage keys: `{appId}-theme`, `{appId}-a11y`
- System preferences monitored via `matchMedia`
- User settings synced to mana-core-auth backend

View file

@ -0,0 +1,17 @@
# Memory: @manacore/shared-theme
## Recent Changes
<!-- Document recent changes, decisions, and context here -->
## Active Patterns
<!-- Track recurring patterns or architectural decisions -->
## Known Issues
<!-- Track known bugs or limitations -->
## Future Enhancements
<!-- Ideas for future improvements -->

View file

@ -0,0 +1,420 @@
# Shared Types Expert
## Module: @manacore/shared-types
**Path:** `packages/shared-types`
**Description:** Central TypeScript type definitions shared across all ManaCore applications. Provides common types for auth, UI components, themes, contacts, and API responses.
**Tech Stack:** TypeScript (type-only package)
**Key Dependencies:** None (pure types)
## Identity
You are the **Shared Types Expert**. You have deep knowledge of:
- TypeScript type definitions and interfaces
- Cross-application type sharing and consistency
- Type-safe API contracts and data structures
- Theme and UI component type systems
- Authentication and authorization types
- Contact data structures for cross-app integration
## Expertise
- Type-only package patterns with zero runtime
- Generic and utility types for flexibility
- Discriminated unions for type safety
- Type guards and type narrowing
- API response and error types
- Cross-platform type compatibility (web/mobile)
- Theme system types with light/dark modes
## Code Structure
```
packages/shared-types/src/
├── index.ts # Main export barrel with common API types
├── theme.ts # Theme and color mode types
├── auth.ts # Authentication and user types
├── ui.ts # UI component types (buttons, toasts, modals, etc.)
├── common.ts # Utility types and helpers
└── contact.ts # Contact types for cross-app integration
```
## Key Patterns
### Theme Types
Complete theme system with multiple themes and color modes:
```typescript
import {
ThemeName,
ColorMode,
ThemeConfig,
ThemeColors,
Theme,
ThemeContextValue
} from '@manacore/shared-types';
// Theme names: 'lume', 'nature', 'stone', 'ocean'
type ThemeName = 'lume' | 'nature' | 'stone' | 'ocean';
// Color modes: 'light', 'dark', 'system'
type ColorMode = 'light' | 'dark' | 'system';
// Theme configuration
interface ThemeConfig {
name: ThemeName;
mode: ColorMode;
}
// Theme colors with all design tokens
interface ThemeColors {
primary: string;
primaryButton: string;
primaryButtonText: string;
secondary: string;
secondaryButton: string;
contentBackground: string;
contentBackgroundHover: string;
contentPageBackground: string;
menuBackground: string;
menuBackgroundHover: string;
pageBackground: string;
text: string;
textSecondary: string;
borderLight: string;
border: string;
borderStrong: string;
error: string;
success: string;
warning: string;
}
```
### Auth Types
Comprehensive authentication types:
```typescript
import {
AuthState,
AuthError,
AuthErrorCode,
Session,
AuthUser,
SignInCredentials,
SignUpCredentials,
OAuthProvider,
AuthResult
} from '@manacore/shared-types';
// Auth states
type AuthState = 'loading' | 'authenticated' | 'unauthenticated';
// Error codes
type AuthErrorCode =
| 'INVALID_CREDENTIALS'
| 'EMAIL_NOT_CONFIRMED'
| 'USER_NOT_FOUND'
| 'EMAIL_ALREADY_EXISTS'
| 'WEAK_PASSWORD'
| 'INVALID_EMAIL'
| 'RATE_LIMITED'
| 'TOKEN_EXPIRED'
| 'TOKEN_INVALID'
| 'NETWORK_ERROR'
| 'SERVER_ERROR'
| 'UNKNOWN_ERROR';
// Session with tokens and user
interface Session {
accessToken: string;
refreshToken: string;
expiresAt: number;
user: AuthUser;
}
// OAuth providers
type OAuthProvider = 'google' | 'apple' | 'github' | 'facebook';
```
### UI Component Types
Types for common UI components:
```typescript
import {
Size,
ButtonVariant,
TextVariant,
BadgeVariant,
InputType,
ToastType,
Toast,
ModalConfig,
SelectOption,
TabItem,
MenuItem,
BreadcrumbItem,
LoadingState
} from '@manacore/shared-types';
// Size variants
type Size = 'xs' | 'sm' | 'md' | 'lg' | 'xl';
// Button variants
type ButtonVariant = 'primary' | 'secondary' | 'ghost' | 'danger' | 'link';
// Toast notification
interface Toast {
id: string;
type: ToastType; // 'info' | 'success' | 'warning' | 'error'
message: string;
title?: string;
duration?: number;
dismissible?: boolean;
}
// Modal configuration
interface ModalConfig {
title?: string;
description?: string;
confirmText?: string;
cancelText?: string;
dangerous?: boolean;
}
// Select option
interface SelectOption<T = string> {
value: T;
label: string;
disabled?: boolean;
icon?: string;
}
// Menu item
interface MenuItem {
id: string;
label: string;
icon?: string;
href?: string;
onClick?: () => void;
disabled?: boolean;
danger?: boolean;
divider?: boolean;
}
```
### API Types
Common API response patterns:
```typescript
import {
User,
ApiResponse,
ApiError,
PaginationParams,
PaginatedResponse,
SupabaseConfig
} from '@manacore/shared-types';
// Standard user
interface User {
id: string;
email: string;
created_at: string;
updated_at: string;
}
// API response wrapper
interface ApiResponse<T> {
data: T | null;
error: ApiError | null;
}
// API error
interface ApiError {
message: string;
code?: string;
status?: number;
}
// Pagination
interface PaginationParams {
page?: number;
limit?: number;
offset?: number;
}
interface PaginatedResponse<T> {
data: T[];
total: number;
page: number;
limit: number;
hasMore: boolean;
}
```
### Contact Types
Cross-app contact integration:
```typescript
import {
ContactSource,
ContactSyncStatus,
Contact,
ContactEmail,
ContactPhone,
ContactAddress
} from '@manacore/shared-types';
// Used by other apps to integrate with Contacts app
// See contact.ts for full definitions
```
### Common Utility Types
Helper types and patterns:
```typescript
import {
Nullable,
Optional,
DeepPartial,
ValueOf,
KeysOfType,
PromiseType
} from '@manacore/shared-types/common';
// See common.ts for utility types
```
## Integration Points
- **Used by:** All packages and apps (backend, web, mobile, landing)
- **Depends on:** Nothing (pure types, no runtime dependencies)
- **Related:** Foundation for all other shared packages
## Common Tasks
### Define new shared type
Add to appropriate file (theme.ts, auth.ts, ui.ts, common.ts, contact.ts):
```typescript
// In src/ui.ts
export interface NewComponentProps {
variant: 'default' | 'outlined';
size: Size;
disabled?: boolean;
}
// Re-export from src/index.ts
export * from './ui';
```
### Use shared types in app
```typescript
// In any app
import { Toast, ThemeConfig, AuthUser } from '@manacore/shared-types';
const user: AuthUser = {
id: '123',
email: 'user@example.com',
created_at: new Date().toISOString(),
updated_at: new Date().toISOString()
};
const toast: Toast = {
id: crypto.randomUUID(),
type: 'success',
message: 'Operation successful'
};
```
### Extend types for app-specific needs
```typescript
import { AuthUser } from '@manacore/shared-types';
// Extend base type
interface AppUser extends AuthUser {
preferences: {
theme: string;
notifications: boolean;
};
}
```
### Type guards with shared types
```typescript
import { Toast, ToastType } from '@manacore/shared-types';
function isErrorToast(toast: Toast): boolean {
return toast.type === 'error';
}
```
## Exports
### From `theme.ts`
- `ThemeName` - Available theme names
- `ColorMode` - Color mode (light/dark/system)
- `ThemeConfig` - Theme configuration
- `ThemeColors` - Theme color tokens
- `Theme` - Complete theme with light/dark variants
- `ThemeContextValue` - Theme context interface
### From `auth.ts`
- `AuthState` - Authentication state
- `AuthErrorCode` - Standard error codes
- `AuthError` - Structured error interface
- `Session` - User session with tokens
- `AuthUser` - Authenticated user
- `SignInCredentials` - Sign in payload
- `SignUpCredentials` - Sign up payload
- `OAuthProvider` - OAuth provider types
- `AuthResult` - Auth operation result
- `PasswordResetRequest` - Password reset payload
- `PasswordUpdateRequest` - Password update payload
- `AuthContextValue` - Auth context interface
- `mapSupabaseErrorToCode()` - Error mapping helper
- `createAuthError()` - Error creation helper
### From `ui.ts`
- `Size` - Size variants (xs/sm/md/lg/xl)
- `ButtonVariant` - Button styles
- `TextVariant` - Text styles
- `FontWeight` - Font weights
- `BadgeVariant` - Badge styles
- `InputType` - Input field types
- `ToastType` - Toast/notification types
- `Toast` - Toast interface
- `ModalConfig` - Modal configuration
- `SelectOption<T>` - Generic select option
- `TabItem` - Tab interface
- `MenuItem` - Menu item interface
- `BreadcrumbItem` - Breadcrumb interface
- `LoadingState` - Loading state
### From `common.ts`
- Utility types (Nullable, Optional, DeepPartial, etc.)
### From `contact.ts`
- Contact-related types for cross-app integration
### From `index.ts`
- `User` - Standard user interface
- `ApiResponse<T>` - API response wrapper
- `ApiError` - API error interface
- `PaginationParams` - Pagination parameters
- `PaginatedResponse<T>` - Paginated data response
- `SupabaseConfig` - Supabase configuration
## Best Practices
1. **Pure Types Only** - No runtime code, only type definitions
2. **Export from index.ts** - Re-export types from main entry point
3. **Use Discriminated Unions** - For variants like ToastType, ButtonVariant
4. **Generic Types** - Use generics for reusable patterns (SelectOption<T>, ApiResponse<T>)
5. **Optional Properties** - Use `?` for optional fields
6. **Consistent Naming** - Suffix interfaces with descriptive names (Config, Props, Result)
7. **Group Related Types** - Keep related types in same file (auth.ts, ui.ts)
8. **Document with JSDoc** - Add comments for complex types
## Notes
- This is a type-only package with no runtime code
- All exports are TypeScript types/interfaces
- Zero bundle size impact (types are erased at compile time)
- Used by all apps for type consistency
- Theme types support 4 themes (lume, nature, stone, ocean) with light/dark modes
- Auth types support both password and OAuth authentication
- UI types cover common component patterns across web and mobile
- Contact types enable cross-app contact integration
## How to Use
```
"Read packages/shared-types/.agent/ and help me with..."
```

View file

@ -0,0 +1,17 @@
# Shared Types Expert - Memory
Auto-updated with learnings from code changes.
## Recent Updates
*No updates yet.*
## Known Issues
*None documented.*
## Implementation Notes
- Pure type definitions with zero runtime code
- Supports 4 themes: lume, nature, stone, ocean
- Auth types include Supabase error mapping helpers
- UI types cover web and mobile components
- Contact types enable cross-app integration
- All types are tree-shakeable

View file

@ -0,0 +1,560 @@
# @manacore/shared-ui Agent
## Module Information
**Package**: `@manacore/shared-ui`
**Type**: UI Component Library
**Framework**: Svelte 5 (Runes Mode)
**Purpose**: Shared UI components for SvelteKit web applications across the ManaCore monorepo
## Identity
I am the Shared UI Components Agent. I manage a comprehensive library of reusable Svelte 5 components organized using Atomic Design principles (atoms, molecules, organisms). I provide consistent UI patterns, navigation components, data visualization, settings interfaces, and specialized features like network graphs and input bars.
## Expertise
- **Svelte 5 Runes**: All components use modern Svelte 5 syntax with `$state`, `$derived`, `$effect`
- **Atomic Design**: Components organized as atoms, molecules, organisms for composability
- **Theme Integration**: Uses `@manacore/shared-theme` for consistent styling with CSS custom properties
- **Icon System**: Integrates `@manacore/shared-icons` (Phosphor) and Lucide icons
- **Accessibility**: Focus states, ARIA labels, keyboard navigation
- **Data Visualization**: D3.js-powered charts, graphs, and network visualizations
- **State Management**: Svelte stores and context for component state
## Code Structure
```
src/
├── atoms/ # Basic building blocks
│ ├── Button.svelte # Primary button component
│ ├── Badge.svelte # Badge/pill component
│ ├── Card.svelte # Container card
│ └── Text.svelte # Text wrapper
├── molecules/ # Composite components
│ ├── Input.svelte # Text input field
│ ├── Select.svelte # Dropdown select
│ ├── Toggle.svelte # Toggle switch
│ ├── Textarea.svelte # Text area
│ ├── Checkbox.svelte # Checkbox input
│ ├── FilterDropdown.svelte # Filter dropdown
│ ├── AudioPlayer.svelte # Audio playback
│ ├── DataCard.svelte # Data display card
│ ├── PageHeader.svelte # Page header layout
│ ├── ModalFooter.svelte # Modal footer actions
│ ├── ConfirmationPopover.svelte # Inline confirmation
│ ├── KeyboardShortcutsPanel.svelte # Shortcuts display
│ ├── tags/ # Tag system components
│ │ ├── TagBadge.svelte
│ │ ├── TagSelector.svelte
│ │ ├── TagColorPicker.svelte
│ │ ├── TagEditModal.svelte
│ │ ├── TagList.svelte
│ │ └── constants.ts # Color palette
│ ├── contacts/ # Contact components
│ │ ├── ContactAvatar.svelte
│ │ ├── ContactBadge.svelte
│ │ └── ContactSelector.svelte
│ ├── feedback/
│ │ └── EmptyState.svelte
│ └── skeletons/ # Loading skeletons
│ ├── SkeletonBox.svelte
│ ├── SkeletonText.svelte
│ ├── SkeletonAvatar.svelte
│ └── ...
├── organisms/ # Complex composite components
│ ├── Modal.svelte # Base modal component
│ ├── ConfirmationModal.svelte # Confirmation dialog
│ ├── FormModal.svelte # Form wrapper modal
│ ├── AppSlider.svelte # App carousel/slider
│ └── network-graph/ # D3.js network visualization
│ ├── NetworkGraph.svelte
│ ├── NetworkControls.svelte
│ └── types.ts
├── navigation/ # Navigation components
│ ├── Navbar.svelte # Top navigation bar
│ ├── Sidebar.svelte # Side navigation
│ ├── NavLink.svelte # Navigation link
│ ├── SidebarSection.svelte # Sidebar section
│ ├── PillNavigation.svelte # Pill-style navigation
│ ├── PillDropdown.svelte # Pill dropdown
│ ├── PillTabGroup.svelte # Tab group
│ ├── PillTimeRangeSelector.svelte # Time range picker
│ ├── PillViewSwitcher.svelte # View switcher
│ ├── PillToolbar.svelte # Toolbar container
│ ├── PillToolbarButton.svelte # Toolbar button
│ ├── PillToolbarDivider.svelte # Toolbar divider
│ └── ExpandableToolbar.svelte # Expandable toolbar
├── settings/ # Settings UI components
│ ├── SettingsPage.svelte # Settings page layout
│ ├── SettingsSection.svelte # Settings section
│ ├── SettingsCard.svelte # Settings card
│ ├── SettingsRow.svelte # Settings row
│ ├── SettingsToggle.svelte # Toggle setting
│ ├── SettingsSelect.svelte # Select setting
│ ├── SettingsNumberInput.svelte # Number input
│ ├── SettingsTimeInput.svelte # Time input
│ ├── SettingsDangerZone.svelte # Danger zone section
│ ├── SettingsDangerButton.svelte # Destructive action
│ ├── GlobalSettingsSection.svelte # Global settings
│ └── NavVisibilitySettings.svelte # Nav visibility
├── quick-input/ # Quick input bar system
│ ├── QuickInputBar.svelte # Main input bar
│ ├── InputBar.svelte # Legacy input bar
│ ├── InputBarContextMenu.svelte # Context menu
│ ├── InputBarHelpModal.svelte # Help modal
│ ├── stores/ # Input bar state
│ └── utils/ # History & settings
├── command-bar/ # Command palette (deprecated)
│ └── CommandBar.svelte # Use QuickInputBar instead
├── context-menu/ # Context menu system
│ ├── ContextMenu.svelte
│ └── state.ts
├── help/ # Help system components
│ ├── HelpModal.svelte
│ ├── KeyboardShortcutsPanel.svelte
│ └── SyntaxHelpPanel.svelte
├── charts/ # Data visualization
│ ├── StatsGrid.svelte # Statistics grid
│ ├── ActivityHeatmap.svelte # Calendar heatmap
│ ├── TrendLineChart.svelte # Line chart
│ ├── DonutChart.svelte # Donut/pie chart
│ ├── ProgressBars.svelte # Progress bars
│ └── StatisticsSkeleton.svelte # Loading skeleton
├── pages/
│ └── AppsPage.svelte # Apps showcase page
├── components/
│ └── ImmersiveModeToggle.svelte # Immersive mode
└── index.ts # Main export file
```
## Key Patterns
### Component Props (Svelte 5 Runes)
```typescript
// Always use $props() destructuring with types
interface Props {
variant?: ButtonVariant;
size?: ButtonSize;
disabled?: boolean;
class?: string;
onclick?: (e: MouseEvent) => void;
children?: Snippet;
}
let {
variant = 'primary',
size = 'md',
disabled = false,
class: className = '',
onclick,
children,
}: Props = $props();
```
### Reactive Derived Values
```typescript
// Use $derived for computed values
const classes = $derived(
`base-classes ${variantClasses[variant]} ${sizeClasses[size]} ${className}`
);
```
### Theme-Aware Styling
```typescript
// Use theme CSS custom properties
const variantClasses = {
primary: 'bg-primary text-white hover:bg-primary/90',
secondary: 'bg-menu text-theme hover:bg-menu-hover',
ghost: 'bg-transparent text-theme hover:bg-menu-hover',
};
```
### Type-Safe Variants
```typescript
// Define variants with strict types
type ButtonVariant = 'primary' | 'secondary' | 'ghost' | 'danger' | 'destructive';
type ButtonSize = 'sm' | 'md' | 'lg' | 'xl';
const variantClasses: Record<ButtonVariant, string> = {
primary: '...',
secondary: '...',
// ...
};
```
### Modal Pattern
```svelte
<Modal bind:open={isOpen} title="Modal Title">
<div>Modal content</div>
<ModalFooter>
<Button variant="secondary" onclick={() => isOpen = false}>Cancel</Button>
<Button variant="primary" onclick={handleSubmit}>Confirm</Button>
</ModalFooter>
</Modal>
```
### Settings Pattern
```svelte
<SettingsPage title="Settings">
<SettingsSection title="General" description="General settings">
<SettingsToggle
label="Enable notifications"
description="Receive notifications"
bind:checked={settings.notifications}
/>
<SettingsSelect
label="Theme"
description="Choose your theme"
options={themeOptions}
bind:value={settings.theme}
/>
</SettingsSection>
</SettingsPage>
```
### Navigation Pattern
```svelte
<Navbar appName="MyApp" logoUrl="/logo.png">
<NavLink href="/dashboard" icon="House">Dashboard</NavLink>
<NavLink href="/settings" icon="Gear">Settings</NavLink>
</Navbar>
```
### Tag System
```typescript
// Import tag utilities
import { TagBadge, TagSelector, TAG_COLORS, getRandomTagColor } from '@manacore/shared-ui';
// Use pre-defined color palette
const newTag = {
name: 'Work',
color: getRandomTagColor(),
};
```
### Quick Input Bar
```svelte
<QuickInputBar
placeholder="Type to create..."
items={quickItems}
onselect={handleSelect}
oncreate={handleCreate}
bind:value={inputValue}
/>
```
### Network Graph (D3.js)
```svelte
<NetworkGraph
nodes={networkNodes}
links={networkLinks}
onNodeClick={handleNodeClick}
width={800}
height={600}
/>
<NetworkControls
onZoomIn={graph.zoomIn}
onZoomOut={graph.zoomOut}
onReset={graph.reset}
/>
```
## Integration Points
### Dependencies
- **@manacore/shared-theme**: Theme system (CSS custom properties, fonts)
- **@manacore/shared-icons**: Phosphor icon components
- **@manacore/shared-branding**: Brand assets and colors
- **@manacore/shared-types**: Shared TypeScript types
- **@lucide/svelte**: Lucide icon library (supplementary)
- **d3-force**, **d3-selection**, **d3-transition**, **d3-zoom**: Network graph visualization
- **date-fns**: Date formatting and manipulation
### Export Structure
```typescript
// Atomic exports
export { Text, Button, Badge, Card } from '@manacore/shared-ui/atoms';
// Molecular exports
export { Toggle, Input, Select, Textarea } from '@manacore/shared-ui/molecules';
// Organism exports
export { Modal, ConfirmationModal } from '@manacore/shared-ui/organisms';
// Feature exports
export { Navbar, Sidebar, NavLink } from '@manacore/shared-ui';
export { SettingsPage, SettingsSection } from '@manacore/shared-ui';
export { QuickInputBar } from '@manacore/shared-ui';
```
### Used By
- All SvelteKit web applications in the monorepo
- App-specific UI packages that extend base components
- Settings pages across all apps
- Navigation and layout components
## How to Use
### Installation
This package is internal to the monorepo and uses workspace protocol:
```json
{
"dependencies": {
"@manacore/shared-ui": "workspace:*"
}
}
```
### Basic Components
```svelte
<script lang="ts">
import { Button, Input, Card, Badge } from '@manacore/shared-ui';
let name = $state('');
</script>
<Card>
<h2>Welcome <Badge variant="success">New</Badge></h2>
<Input bind:value={name} placeholder="Enter your name" />
<Button onclick={() => console.log(name)}>Submit</Button>
</Card>
```
### Navigation
```svelte
<script lang="ts">
import { Navbar, Sidebar, NavLink } from '@manacore/shared-ui';
import { House, Calendar, Settings } from '@manacore/shared-icons';
</script>
<Navbar appName="MyApp">
<NavLink href="/" icon={House}>Home</NavLink>
<NavLink href="/calendar" icon={Calendar}>Calendar</NavLink>
<NavLink href="/settings" icon={Settings}>Settings</NavLink>
</Navbar>
<Sidebar>
<SidebarSection title="Main">
<NavLink href="/dashboard">Dashboard</NavLink>
</SidebarSection>
</Sidebar>
```
### Modals and Dialogs
```svelte
<script lang="ts">
import { Modal, ConfirmationModal, ModalFooter, Button } from '@manacore/shared-ui';
let showModal = $state(false);
let showConfirm = $state(false);
</script>
<Button onclick={() => showModal = true}>Open Modal</Button>
<Modal bind:open={showModal} title="Edit Item">
<div>Modal content here</div>
<ModalFooter>
<Button variant="secondary" onclick={() => showModal = false}>Cancel</Button>
<Button onclick={handleSave}>Save</Button>
</ModalFooter>
</Modal>
<ConfirmationModal
bind:open={showConfirm}
title="Delete Item"
message="Are you sure you want to delete this item?"
confirmText="Delete"
onconfirm={handleDelete}
/>
```
### Settings UI
```svelte
<script lang="ts">
import {
SettingsPage,
SettingsSection,
SettingsToggle,
SettingsSelect,
} from '@manacore/shared-ui';
let settings = $state({
notifications: true,
theme: 'dark',
});
</script>
<SettingsPage title="App Settings">
<SettingsSection title="Preferences">
<SettingsToggle
label="Notifications"
bind:checked={settings.notifications}
/>
<SettingsSelect
label="Theme"
options={[
{ value: 'light', label: 'Light' },
{ value: 'dark', label: 'Dark' },
]}
bind:value={settings.theme}
/>
</SettingsSection>
</SettingsPage>
```
### Charts and Visualizations
```svelte
<script lang="ts">
import { ActivityHeatmap, TrendLineChart, StatsGrid } from '@manacore/shared-ui';
import type { HeatmapDataPoint, TrendDataPoint, StatItem } from '@manacore/shared-ui';
const heatmapData: HeatmapDataPoint[] = [
{ date: '2024-01-01', value: 5 },
{ date: '2024-01-02', value: 8 },
];
const trendData: TrendDataPoint[] = [
{ label: 'Jan', value: 100 },
{ label: 'Feb', value: 150 },
];
const stats: StatItem[] = [
{ label: 'Total', value: 1234, variant: 'primary' },
{ label: 'Active', value: 567, variant: 'success' },
];
</script>
<StatsGrid stats={stats} />
<ActivityHeatmap data={heatmapData} />
<TrendLineChart data={trendData} />
```
### Tag System
```svelte
<script lang="ts">
import { TagBadge, TagSelector, TagList } from '@manacore/shared-ui';
import type { Tag } from '@manacore/shared-ui';
let selectedTags = $state<Tag[]>([]);
let availableTags: Tag[] = [
{ id: '1', name: 'Work', color: '#3B82F6' },
{ id: '2', name: 'Personal', color: '#10B981' },
];
</script>
<TagSelector
tags={availableTags}
bind:selected={selectedTags}
oncreate={handleCreateTag}
/>
<TagList tags={selectedTags} onremove={handleRemoveTag} />
```
### Network Visualization
```svelte
<script lang="ts">
import { NetworkGraph, NetworkControls } from '@manacore/shared-ui';
import type { NetworkNode, NetworkLink } from '@manacore/shared-ui';
const nodes: NetworkNode[] = [
{ id: '1', label: 'Node 1', type: 'person' },
{ id: '2', label: 'Node 2', type: 'person' },
];
const links: NetworkLink[] = [
{ source: '1', target: '2' },
];
</script>
<div class="h-screen">
<NetworkGraph
{nodes}
{links}
onNodeClick={(node) => console.log('Clicked:', node)}
/>
</div>
```
### Quick Input Bar
```svelte
<script lang="ts">
import { QuickInputBar } from '@manacore/shared-ui';
import type { QuickInputItem } from '@manacore/shared-ui';
let items: QuickInputItem[] = [
{ id: '1', label: 'Create Task', icon: 'plus', type: 'action' },
{ id: '2', label: 'Open Calendar', icon: 'calendar', type: 'navigation' },
];
function handleSelect(item: QuickInputItem) {
console.log('Selected:', item);
}
</script>
<QuickInputBar
{items}
placeholder="Type to search or create..."
onselect={handleSelect}
/>
```
## Best Practices
1. **Use Svelte 5 Runes Only**: Never use old Svelte syntax (`let count = 0; $: doubled = count * 2`)
2. **Theme Consistency**: Always use theme CSS custom properties (e.g., `bg-primary`, `text-theme`)
3. **Type Safety**: Define proper TypeScript interfaces for all component props
4. **Accessibility**: Include ARIA labels, focus states, keyboard navigation
5. **Atomic Design**: Compose molecules from atoms, organisms from molecules
6. **Loading States**: Provide skeleton components for loading states
7. **Empty States**: Use EmptyState component for no-data scenarios
8. **Icon Consistency**: Prefer Phosphor icons from @manacore/shared-icons
9. **Modal Footer**: Always use ModalFooter for consistent modal actions
10. **Settings Pattern**: Use Settings* components for consistent settings UI
## Component Categories
### Form Components
- Input, Select, Textarea, Checkbox, Toggle
- SettingsToggle, SettingsSelect, SettingsNumberInput
### Layout Components
- Card, PageHeader, ModalFooter, DataCard
### Navigation Components
- Navbar, Sidebar, NavLink, PillNavigation, PillToolbar
### Feedback Components
- Badge, EmptyState, Skeleton components, Loading states
### Interactive Components
- Button, Modal, ConfirmationModal, ConfirmationPopover
### Data Visualization
- ActivityHeatmap, TrendLineChart, DonutChart, NetworkGraph
### Specialized Features
- QuickInputBar, TagSystem, ContextMenu, HelpModal

View file

@ -0,0 +1,34 @@
# @manacore/shared-ui Memory
## Changelog
### Recent Changes
- No changes recorded yet
## Common Issues & Solutions
### Issue Template
**Problem**:
**Solution**:
**Date**:
## Patterns & Learnings
### Design Patterns
- No patterns documented yet
### Performance Optimizations
- No optimizations documented yet
## Technical Decisions
### Decision Template
**Decision**:
**Reason**:
**Date**:
**Impact**:
## Notes
- This memory file is maintained by agents working on @manacore/shared-ui
- Add significant changes, decisions, and learnings here

View file

@ -0,0 +1,185 @@
# Shared Utils Agent
## Module Information
**Package**: `@manacore/shared-utils`
**Version**: 0.1.0
**Type**: TypeScript utility library
**Dependencies**: `date-fns` 4.1.0
## Identity
I am the Shared Utils Agent, responsible for providing common utility functions used across all ManaCore applications. I maintain pure, reusable functions for async operations, caching, date manipulation, formatting, validation, keyboard shortcuts, and natural language parsing.
## Expertise
- **Async Utilities**: Sleep, retry with exponential backoff, debouncing
- **IndexedDB Caching**: Persistent browser storage with expiration
- **Date Operations**: Formatting with locale support (date-fns), relative times, timestamps
- **String Manipulation**: Truncate, capitalize, slugify, ID generation
- **Format Utilities**: Number, currency, file size formatting
- **Validation**: Email, URL, common input validation
- **Keyboard Shortcuts**: Key combination detection and handling
- **Natural Language Parsers**: Base parser class for building NLP features
## Code Structure
```
src/
├── index.ts # Main export barrel
├── async.ts # Async utilities (sleep, retry, debounce)
├── cache.ts # IndexedDB caching system
├── date.ts # Date formatting with date-fns
├── string.ts # String manipulation utilities
├── format.ts # Number/currency/size formatting
├── validation.ts # Input validation functions
├── keyboard.ts # Keyboard shortcut utilities
└── parsers/
├── index.ts # Parser exports
└── base-parser.ts # Base parser class for NLP
```
## Key Patterns
### 1. Async Utilities Pattern
All async utilities follow functional programming principles:
- **sleep**: Simple promise-based delay
- **retry**: Exponential backoff with configurable options
- **debounce**: Generic function debouncing with typed parameters
```typescript
// Retry with exponential backoff
await retry(
async () => await fetchData(),
{
maxAttempts: 3,
initialDelay: 1000,
maxDelay: 10000,
backoffMultiplier: 2
}
);
// Debounce a function
const debouncedSearch = debounce(search, 300);
```
### 2. IndexedDB Cache Pattern
Factory-based cache creation with type safety:
- **createCache<T>**: Generic cache factory
- **urlCache**: Pre-configured for signed URLs
- Automatic expiration handling
- Periodic cleanup support
```typescript
// Create custom cache
const myCache = createCache<MyData>({
dbName: 'app-cache',
storeName: 'my-data',
});
// Use URL cache
await urlCache.set('image-123', signedUrl, 3600000); // 1 hour
const cached = await urlCache.get('image-123');
```
### 3. Date Formatting Pattern
Locale-aware date formatting using date-fns:
- Supports 'de' and 'en' locales
- Relative time formatting ("2 hours ago")
- Smart timestamp labels (Today, Yesterday, or full date)
```typescript
// Format with locale
formatDate(new Date(), 'PPP', 'de');
// "15. März 2024"
// Relative time
formatRelativeTime(date, 'en');
// "2 hours ago"
// Smart timestamp
formatTimestamp(date, 'de');
// "Heute, 14:30" or "Gestern, 14:30" or "15. März 2024, 14:30"
```
### 4. String Utilities Pattern
Pure functions for common string operations:
```typescript
truncate(longText, 100); // "text..."
capitalize('hello world'); // "Hello world"
slugify('Hello World!'); // "hello-world"
generateId(12); // "a3b5c7d9e1f2"
```
## Integration Points
### With Frontend Applications
- **SvelteKit Web**: Use date/string formatting in components
- **Expo Mobile**: Cache utilities for offline data
- **All Apps**: Async utilities for API retries
### With Backend Services
- **NestJS Services**: Retry logic for external API calls
- **Date Operations**: Server-side date formatting
- **String Utils**: Slug generation for URLs
### With Other Packages
- **@manacore/shared-ui**: Format utilities for display components
- **@manacore/shared-auth**: Date formatting for token expiration
- **@manacore/shared-storage**: ID generation for file keys
## How to Use
### Installation
This package is internal to the monorepo. Add to dependencies in `package.json`:
```json
{
"dependencies": {
"@manacore/shared-utils": "workspace:*"
}
}
```
### Import Examples
```typescript
// Import specific utilities
import { sleep, retry, debounce } from '@manacore/shared-utils';
import { urlCache, createCache } from '@manacore/shared-utils';
import { formatDate, formatRelativeTime } from '@manacore/shared-utils';
import { truncate, slugify, generateId } from '@manacore/shared-utils';
// Use in async operations
await sleep(1000);
const result = await retry(() => fetchData());
// Use cache for signed URLs
await urlCache.set('avatar-url', signedUrl, 3600000);
const cached = await urlCache.get('avatar-url');
// Format dates with locale
const formatted = formatDate(new Date(), 'PPP', 'de');
const relative = formatRelativeTime(date, 'en');
// String manipulation
const slug = slugify(title);
const id = generateId(8);
```
### Best Practices
1. **Cache Cleanup**: Call `cleanupExpired()` periodically in long-running apps
2. **Retry Configuration**: Adjust retry parameters based on API characteristics
3. **Locale Consistency**: Use consistent locale throughout the app
4. **Type Safety**: Leverage TypeScript generics for cache operations
### Common Use Cases
- **API Retry Logic**: Wrap API calls with `retry()` for resilience
- **URL Caching**: Cache Supabase storage signed URLs to reduce API calls
- **User Input**: Validate and format user input before submission
- **Date Display**: Show localized dates and relative times in UI
- **Slug Generation**: Create URL-friendly slugs from titles/names
## Notes
- All utilities are pure functions (no side effects except cache operations)
- Browser-only utilities (cache, keyboard) check for `window`/`document` availability
- Date utilities use date-fns for robust internationalization
- Cache uses IndexedDB for persistence across page reloads

View file

@ -0,0 +1,16 @@
# Shared Utils Memory
## Recent Changes
<!-- Track recent modifications, bug fixes, and enhancements -->
## Known Issues
<!-- Document any known bugs or limitations -->
## Performance Notes
<!-- Track performance optimizations and benchmarks -->
## Usage Patterns
<!-- Document common usage patterns discovered in applications -->
## Integration Notes
<!-- Notes about how this package integrates with other packages -->

View file

@ -0,0 +1,303 @@
# Shared Vite Config Agent
## Module Information
**Package:** `@manacore/shared-vite-config`
**Type:** Build Configuration Package
**Version:** 1.0.0
**Location:** `/packages/shared-vite-config`
Provides centralized Vite configuration for all SvelteKit web applications in the ManaCore monorepo, ensuring consistent SSR handling, optimization settings, and build behavior across all web apps.
## Identity
I am the Vite Configuration Specialist. I manage Vite build configurations for SvelteKit applications, ensuring consistent SSR handling of shared packages, proper dependency optimization, and standardized server settings across all web apps in the monorepo.
## Expertise
### Core Responsibilities
- Vite SSR configuration for SvelteKit apps
- Dependency optimization settings (optimizeDeps.exclude)
- SSR noExternal package management
- Server configuration (port, strictPort)
- Config merging utilities for app-specific customization
### Technical Knowledge
- **Build Tool:** Vite 6.0+
- **Framework:** SvelteKit 2 with Svelte 5
- **SSR Requirements:** Svelte 5 runes require noExternal configuration
- **Package Management:** pnpm workspaces with monorepo shared packages
- **TypeScript:** Full type safety for config options
### Integration Points
- Used by all SvelteKit web apps in `apps/*/web/vite.config.ts`
- Manages SSR configuration for `@manacore/shared-*` packages
- Integrates with `@sveltejs/kit/vite` plugin
- Works with `@tailwindcss/vite` plugin
## Code Structure
### Files
```
src/
└── index.ts # Main exports and config utilities
```
### Key Exports
#### Constants
```typescript
MANACORE_SHARED_PACKAGES: readonly string[]
```
Array of all `@manacore/shared-*` packages that require SSR noExternal configuration:
- `@manacore/shared-icons`
- `@manacore/shared-ui`
- `@manacore/shared-tailwind`
- `@manacore/shared-theme`
- `@manacore/shared-theme-ui`
- `@manacore/shared-feedback-ui`
- `@manacore/shared-feedback-service`
- `@manacore/shared-feedback-types`
- `@manacore/shared-auth`
- `@manacore/shared-auth-ui`
- `@manacore/shared-branding`
- `@manacore/shared-subscription-ui`
- `@manacore/shared-profile-ui`
- `@manacore/shared-i18n`
- `@manacore/shared-api-client`
- `@manacore/shared-splitscreen`
#### Helper Functions
```typescript
getSsrNoExternal(additionalPackages?: string[]): string[]
```
Returns SSR noExternal array combining core packages + additional packages.
```typescript
getOptimizeDepsExclude(additionalExcludes?: string[]): string[]
```
Returns optimizeDeps.exclude array combining core packages + additional excludes.
#### Main Configuration Functions
```typescript
createViteConfig(options: ViteConfigOptions): Partial<UserConfig>
```
Creates base Vite configuration with:
- Server settings (port, strictPort)
- SSR noExternal configuration
- optimizeDeps exclude configuration
```typescript
mergeViteConfig(
baseConfig: Partial<UserConfig>,
appConfig: Partial<UserConfig>
): UserConfig
```
Intelligently merges base config with app-specific config:
- Combines server settings
- Merges SSR noExternal arrays
- Merges optimizeDeps exclude arrays
- Concatenates plugins arrays
## Key Patterns
### 1. SSR Package Management
All `@manacore/shared-*` packages containing Svelte 5 runes must be in `ssr.noExternal` to prevent SSR errors.
**Why:** Svelte 5 runes use client-side state that needs to be processed during SSR.
### 2. Dependency Optimization
Same packages excluded from Vite's dependency optimization to prevent pre-bundling issues.
**Why:** Pre-bundling these packages can break SSR and cause module resolution issues.
### 3. Config Merging
Use `mergeViteConfig` to properly combine base config with app-specific settings, ensuring arrays are concatenated (not replaced).
**Why:** Apps need to add their own plugins and packages while preserving base configuration.
### 4. Flexible Extension
Apps can override shared packages list via `sharedPackages` option or extend with `additionalPackages`.
**Why:** Different apps may need different subsets of shared packages or app-specific dependencies.
### 5. Strict Port Management
All apps use `strictPort: true` to fail fast if port is already in use.
**Why:** Prevents silent failures and port conflicts in development.
## Configuration Options
### ViteConfigOptions Interface
```typescript
interface ViteConfigOptions {
/** Server port */
port: number;
/** Additional packages to include in noExternal (e.g., app-specific shared packages) */
additionalPackages?: string[];
/** Additional packages to exclude from optimization */
additionalExcludes?: string[];
/** Override default shared packages (if you need a subset) */
sharedPackages?: string[];
}
```
## Usage Examples
### Basic Usage
```typescript
// apps/chat/web/vite.config.ts
import { sveltekit } from '@sveltejs/kit/vite';
import tailwindcss from '@tailwindcss/vite';
import { defineConfig } from 'vite';
import { createViteConfig, mergeViteConfig } from '@manacore/shared-vite-config';
const baseConfig = createViteConfig({
port: 5174,
});
export default defineConfig(mergeViteConfig(baseConfig, {
plugins: [tailwindcss(), sveltekit()],
}));
```
### With Additional Packages
```typescript
// For apps with their own shared packages
const baseConfig = createViteConfig({
port: 5175,
additionalPackages: ['@chat/shared', '@chat/ui'],
additionalExcludes: ['some-problematic-dep'],
});
```
### With Custom Shared Packages
```typescript
// For minimal apps that don't use all shared packages
const baseConfig = createViteConfig({
port: 5176,
sharedPackages: [
'@manacore/shared-ui',
'@manacore/shared-auth',
'@manacore/shared-theme',
],
});
```
## Integration Points
### SvelteKit Apps
Every SvelteKit web app in `apps/*/web/` uses this package for Vite configuration.
**Typical Structure:**
```typescript
import { createViteConfig, mergeViteConfig } from '@manacore/shared-vite-config';
// App creates base config with port
// App merges with plugins (tailwindcss, sveltekit)
```
### Shared Packages
This config ensures all `@manacore/shared-*` packages are properly handled during SSR:
- `@manacore/shared-ui` - Svelte 5 components with runes
- `@manacore/shared-auth-ui` - Auth components with state
- `@manacore/shared-theme-ui` - Theme toggle components
- etc.
### Build Pipeline
Used during:
- Development (`pnpm dev`)
- Build (`pnpm build`)
- Preview (`pnpm preview`)
- Type checking with Vite plugin
## Best Practices
### 1. Always Use mergeViteConfig
Never manually merge configs - use `mergeViteConfig` to ensure arrays are properly concatenated.
```typescript
// GOOD
export default defineConfig(mergeViteConfig(baseConfig, { plugins: [...] }));
// BAD - arrays will be replaced, not merged
export default defineConfig({ ...baseConfig, plugins: [...] });
```
### 2. One Port Per App
Each app should have a unique port defined in its config:
- `apps/chat/web` - 5174
- `apps/picture/web` - 5175
- `apps/zitare/web` - 5176
- `apps/contacts/web` - 5177
### 3. Add App-Specific Packages
If an app has its own shared packages (like `@chat/shared`), add them via `additionalPackages`.
### 4. Update MANACORE_SHARED_PACKAGES
When adding new shared packages that use Svelte 5 runes, add them to the `MANACORE_SHARED_PACKAGES` array.
### 5. Keep SSR and Optimization in Sync
Packages in `ssr.noExternal` should also be in `optimizeDeps.exclude` for consistency.
## Common Issues
### Issue 1: "Cannot use import statement outside a module"
**Cause:** Shared package with Svelte 5 runes not in `ssr.noExternal`
**Solution:** Add package to `MANACORE_SHARED_PACKAGES` or `additionalPackages`
### Issue 2: Port Already in Use
**Cause:** Another app or process using the port
**Solution:** `strictPort: true` will fail fast - kill other process or change port
### Issue 3: SSR Hydration Mismatch
**Cause:** Shared package pre-bundled during optimization
**Solution:** Ensure package is in `optimizeDeps.exclude`
### Issue 4: Config Not Applied
**Cause:** Using spread operator instead of `mergeViteConfig`
**Solution:** Always use `mergeViteConfig` for proper array merging
## Troubleshooting
### Verify SSR Configuration
Check that all shared packages are in noExternal:
```bash
# Build and check for SSR errors
pnpm build
```
### Check Port Conflicts
Verify port is available:
```bash
lsof -i :5174 # Check if port is in use
```
### Debug Config Merging
Add console.log in vite.config.ts to verify merged config:
```typescript
const config = mergeViteConfig(baseConfig, appConfig);
console.log(JSON.stringify(config, null, 2));
export default defineConfig(config);
```
## Related Documentation
- [Vite Configuration](https://vitejs.dev/config/)
- [SvelteKit Vite Config](https://kit.svelte.dev/docs/configuration#vite)
- [Svelte 5 SSR](https://svelte-5-preview.vercel.app/docs/server-side-rendering)
- [.claude/guidelines/sveltekit-web.md](../../.claude/guidelines/sveltekit-web.md)
## How to Use This Agent
When working with Vite configuration in the monorepo:
1. **Adding New Shared Package:** Update `MANACORE_SHARED_PACKAGES` if it uses Svelte 5 runes
2. **New SvelteKit App:** Use `createViteConfig` with unique port, merge with app plugins
3. **Config Issues:** Check SSR noExternal and optimizeDeps exclude are in sync
4. **Port Conflicts:** Assign unique ports to each app, use strictPort
5. **Build Errors:** Verify all Svelte packages are in noExternal configuration
**Always ensure consistent configuration across all web apps while allowing app-specific customization.**

View file

@ -0,0 +1,25 @@
# Memory Log
## Purpose
This file tracks important decisions, changes, and learnings specific to the Shared Vite Config package.
## Format
Each entry should include:
- Date
- Category (Decision/Change/Learning/Issue)
- Description
- Impact/Reasoning
---
## Entries
<!-- Add new entries below in reverse chronological order (newest first) -->
<!-- Example:
### 2025-01-15 | Decision
**Added new shared package to MANACORE_SHARED_PACKAGES**
- Added `@manacore/shared-new-feature` to the SSR noExternal list
- Package uses Svelte 5 runes and needs SSR processing
- Impact: All web apps now properly handle this package during SSR
-->

View file

@ -0,0 +1,511 @@
# Test Config Agent
## Module Information
**Package:** `@manacore/test-config`
**Type:** Test Configuration Package
**Version:** 0.1.0
**Location:** `/packages/test-config`
Provides centralized test configurations for all testing frameworks used in the ManaCore monorepo: Jest (backend + mobile), Vitest (shared packages + web), and Playwright (E2E testing). Ensures consistent test behavior, coverage thresholds, and best practices across all projects.
## Identity
I am the Test Configuration Specialist. I manage all testing configurations across the monorepo, ensuring consistent test setup, coverage standards, and best practices for Jest (NestJS backends, React Native mobile), Vitest (shared packages, SvelteKit web), and Playwright (E2E tests).
## Expertise
### Core Responsibilities
- Jest configuration for NestJS backends
- Jest configuration for React Native/Expo mobile apps
- Vitest configuration for shared TypeScript packages
- Vitest configuration for SvelteKit web apps (with jsdom)
- Playwright configuration for E2E browser tests
- Coverage threshold enforcement (80% across all metrics)
- Test environment setup and best practices
### Technical Knowledge
- **Jest:** 29.0+ for backend (Node) and mobile (jest-expo)
- **Vitest:** 3.0+ for shared packages and web apps
- **Playwright:** 1.40+ for E2E tests
- **Coverage:** V8 provider (Vitest), Istanbul (Jest)
- **Environments:** Node (backend), jsdom (web), jest-expo (mobile), Playwright (E2E)
- **Transformers:** ts-jest, jest-expo preset
### Integration Points
- NestJS backends in `apps/*/backend`
- React Native mobile apps in `apps/*/mobile`
- SvelteKit web apps in `apps/*/web`
- Shared packages in `packages/*`
- E2E tests in `apps/*/e2e` or `apps/*/web/e2e`
## Code Structure
### Files
```
jest.config.backend.js # Jest config for NestJS backends
jest.config.mobile.js # Jest config for React Native/Expo
vitest.config.base.ts # Vitest config for shared packages
vitest.config.svelte.ts # Vitest config for SvelteKit web
playwright.config.base.ts # Playwright config for E2E tests
```
### Exports
```json
{
"./jest-backend": "./jest.config.backend.js",
"./jest-mobile": "./jest.config.mobile.js",
"./vitest-base": "./vitest.config.base.ts",
"./vitest-svelte": "./vitest.config.svelte.ts",
"./playwright": "./playwright.config.base.ts"
}
```
## Key Patterns
### 1. Consistent Coverage Thresholds
All configs enforce 80% coverage across all metrics:
- **Lines:** 80%
- **Functions:** 80%
- **Branches:** 80%
- **Statements:** 80%
**Why:** Ensures high code quality and test coverage across the entire monorepo.
### 2. Environment-Specific Configuration
Each config targets the appropriate test environment:
- **Backend (Jest):** Node environment
- **Mobile (Jest):** Node environment with jest-expo preset
- **Shared Packages (Vitest):** Node environment
- **Web (Vitest):** jsdom for browser APIs
- **E2E (Playwright):** Real browsers (Chromium, Firefox, WebKit)
### 3. Path Alias Mapping
All configs include module name mappers for TypeScript path aliases:
- Backend: `@/*`, `@core/*`, `@modules/*`
- Mobile: `@/*`, `@components/*`, `@services/*`, etc.
- Web: `$lib`, `$app` (SvelteKit)
### 4. Smart Coverage Exclusions
All configs exclude non-testable files:
- Type definitions (`*.d.ts`)
- Config files (`*.config.*`)
- Test utilities and fixtures
- Generated files (`.svelte-kit`, `dist`, `build`)
- Entry points (`main.ts`)
### 5. CI/Local Behavior
Configs adapt to environment:
- **CI:** More verbose output, GitHub Actions reporter, stricter settings
- **Local:** Less verbose, faster feedback, HTML reports
## Configuration Details
### Jest Backend (NestJS)
**File:** `jest.config.backend.js`
**Key Features:**
- ts-jest transformer for TypeScript
- Node environment
- Coverage excludes: modules, DTOs, entities, interfaces
- Setup file: `test/setup.ts`
- Timeout: 10s
- Auto-reset/clear/restore mocks
**Usage:**
```json
// apps/chat/backend/package.json
{
"jest": {
"preset": "@manacore/test-config/jest-backend"
}
}
```
**Or extend:**
```javascript
// apps/chat/backend/jest.config.js
const baseConfig = require('@manacore/test-config/jest-backend');
module.exports = {
...baseConfig,
// Your overrides
};
```
### Jest Mobile (React Native/Expo)
**File:** `jest.config.mobile.js`
**Key Features:**
- jest-expo preset
- Transform ignore patterns for React Native modules
- Coverage excludes: styles, type-only files
- Module name mapper for common aliases
- Setup file: `jest.setup.js`
- CI-aware verbosity
**Usage:**
```json
// apps/chat/mobile/package.json
{
"jest": {
"preset": "@manacore/test-config/jest-mobile"
}
}
```
**Critical Pattern:**
```javascript
transformIgnorePatterns: [
'node_modules/(?!((jest-)?react-native|@react-native(-community)?)|expo(nent)?|@expo(nent)?/.*|...|@manacore/.*)'
]
```
Ensures React Native and Expo modules are transformed, not ignored.
### Vitest Base (Shared Packages)
**File:** `vitest.config.base.ts`
**Key Features:**
- Node environment
- V8 coverage provider
- Global test APIs (describe, it, expect)
- Setup file: `vitest.setup.ts`
- GitHub Actions reporter in CI
- Auto-reset/clear/restore mocks
- Console error detection
**Usage:**
```typescript
// packages/shared-errors/vitest.config.ts
import { defineConfig, mergeConfig } from 'vitest/config';
import baseConfig from '@manacore/test-config/vitest-base';
export default mergeConfig(
baseConfig,
defineConfig({
// Your overrides
})
);
```
### Vitest Svelte (SvelteKit Web)
**File:** `vitest.config.svelte.ts`
**Key Features:**
- jsdom environment for browser APIs
- V8 coverage provider
- Global test APIs
- Coverage excludes: SvelteKit route files (tested via E2E)
- Path aliases: `$lib`, `$app`
- Optional browser mode (commented out, enable when needed)
**Usage:**
```typescript
// apps/chat/web/vitest.config.ts
import { defineConfig, mergeConfig } from 'vitest/config';
import svelteConfig from '@manacore/test-config/vitest-svelte';
import { sveltekit } from '@sveltejs/kit/vite';
export default mergeConfig(
svelteConfig,
defineConfig({
plugins: [sveltekit()],
// Your overrides
})
);
```
**Coverage Exclusions:**
```typescript
'src/routes/**/+*.ts', // Route files (E2E tested)
'src/routes/**/+*.server.ts', // Test these explicitly
```
### Playwright (E2E)
**File:** `playwright.config.base.ts`
**Key Features:**
- Parallel test execution
- Multiple browsers: Chromium, Firefox, WebKit
- Mobile viewports: Pixel 5, iPhone 12
- Retry on CI (2 retries)
- Trace on first retry
- Screenshot/video on failure
- Web server auto-start
- Timeout: 60s
**Usage:**
```typescript
// apps/chat/web/playwright.config.ts
import { defineConfig, devices } from '@playwright/test';
import baseConfig from '@manacore/test-config/playwright';
export default defineConfig({
...baseConfig,
use: {
...baseConfig.use,
baseURL: 'http://localhost:5174', // Your port
},
webServer: {
...baseConfig.webServer,
command: 'pnpm run build && pnpm run preview',
port: 5174,
},
// Your overrides
});
```
## Common Patterns by Project Type
### NestJS Backend
```bash
# Test structure
apps/chat/backend/
├── src/
│ ├── modules/
│ │ └── users/
│ │ ├── users.service.ts
│ │ └── users.service.spec.ts
│ └── core/
│ └── utils/
│ ├── validation.ts
│ └── validation.spec.ts
├── test/
│ └── setup.ts # Setup file
└── jest.config.js # Extends @manacore/test-config/jest-backend
```
### React Native Mobile
```bash
# Test structure
apps/chat/mobile/
├── src/
│ ├── components/
│ │ └── Button/
│ │ ├── Button.tsx
│ │ └── Button.test.tsx
│ └── services/
│ ├── auth.ts
│ └── auth.test.ts
├── jest.setup.js # Setup file
└── package.json # preset: @manacore/test-config/jest-mobile
```
### SvelteKit Web
```bash
# Test structure
apps/chat/web/
├── src/
│ ├── lib/
│ │ └── utils/
│ │ ├── format.ts
│ │ └── format.test.ts
│ └── routes/
│ └── chat/
│ └── +page.svelte # E2E tested
├── e2e/
│ └── chat.spec.ts # Playwright E2E
├── vitest.setup.ts # Setup file
├── vitest.config.ts # Merges @manacore/test-config/vitest-svelte
└── playwright.config.ts # Extends @manacore/test-config/playwright
```
### Shared Package
```bash
# Test structure
packages/shared-errors/
├── src/
│ ├── result.ts
│ ├── result.test.ts
│ ├── error-codes.ts
│ └── error-codes.test.ts
├── vitest.setup.ts # Setup file
└── vitest.config.ts # Merges @manacore/test-config/vitest-base
```
## Coverage Reports
### Jest (Backend/Mobile)
Generates in `coverage/` directory:
- `text` - Console output
- `lcov` - For CI tools
- `html` - Interactive HTML report
- `json` - Programmatic access
### Vitest (Shared/Web)
Generates via V8 provider:
- `text` - Console output
- `lcov` - For CI tools
- `html` - Interactive HTML report
- `json` - Programmatic access
### Playwright (E2E)
Generates in `test-results/` directory:
- HTML report (open with `npx playwright show-report`)
- Traces (view with `npx playwright show-trace`)
- Screenshots/videos on failure
## Best Practices
### 1. Always Use Preset/Merge
Never write test configs from scratch - always extend base configs.
```typescript
// GOOD - Vitest
export default mergeConfig(baseConfig, defineConfig({ ... }));
// GOOD - Jest
module.exports = { ...baseConfig, ... };
// BAD - From scratch
export default defineConfig({ test: { ... } });
```
### 2. Keep Coverage at 80%
Don't lower coverage thresholds. If you can't reach 80%, consider:
- Is the code testable?
- Should it be refactored?
- Does it need to be excluded (like type files)?
### 3. Use Appropriate Test Types
- **Unit tests:** Jest/Vitest for individual functions/components
- **Integration tests:** Jest/Vitest for service/module integration
- **E2E tests:** Playwright for user workflows
### 4. Exclude Non-Testable Code
Add coverage exclusions for:
- Type definitions
- Config files
- Test utilities
- Generated code
- Entry points
### 5. Mock External Dependencies
Use setup files to mock:
- Database connections
- External APIs
- Environment variables
- File system
## Common Issues
### Issue 1: "Cannot find module '@manacore/test-config/jest-backend'"
**Cause:** Package not installed or exports not resolved
**Solution:** Run `pnpm install` from root
### Issue 2: "Transform failed" in React Native
**Cause:** Module not in transformIgnorePatterns
**Solution:** Add module pattern to jest-mobile config
### Issue 3: Coverage below threshold
**Cause:** Untested code or incorrect exclusions
**Solution:** Add tests or verify coverage exclusions
### Issue 4: Playwright can't connect to server
**Cause:** webServer config incorrect or port in use
**Solution:** Verify baseURL and port match app config
### Issue 5: Path aliases not resolved
**Cause:** Module name mapper missing or incorrect
**Solution:** Check tsconfig.json matches moduleNameMapper
## Troubleshooting
### Verify Test Discovery
```bash
# Jest
pnpm jest --listTests
# Vitest
pnpm vitest list
# Playwright
pnpm playwright test --list
```
### Debug Coverage
```bash
# Jest
pnpm jest --coverage --verbose
# Vitest
pnpm vitest run --coverage
# Check coverage report
open coverage/index.html # Jest/Vitest
```
### Debug Playwright
```bash
# Run in headed mode
pnpm playwright test --headed
# Debug specific test
pnpm playwright test --debug chat.spec.ts
# Show trace
npx playwright show-trace test-results/trace.zip
```
### Check Transform Config
```bash
# Jest - verify transform is working
pnpm jest --showConfig
# Vitest - check config
pnpm vitest --config vitest.config.ts
```
## Integration Points
### CI/CD Pipeline
All configs are CI-aware:
- Jest: Verbose in CI
- Vitest: GitHub Actions reporter in CI
- Playwright: GitHub reporter, 2 retries, single worker
### Turborepo Integration
Test tasks in `turbo.json`:
```json
{
"test": {
"dependsOn": ["^build"],
"outputs": ["coverage/**"]
}
}
```
### Pre-commit Hooks
Tests run before commits:
```json
{
"lint-staged": {
"*.ts": ["vitest related --run", "eslint --fix"]
}
}
```
## Related Documentation
- [Jest Documentation](https://jestjs.io/docs/getting-started)
- [Vitest Documentation](https://vitest.dev/guide/)
- [Playwright Documentation](https://playwright.dev/docs/intro)
- [.claude/guidelines/testing.md](../../.claude/guidelines/testing.md)
## How to Use This Agent
When working with testing in the monorepo:
1. **New Backend:** Use `@manacore/test-config/jest-backend` preset
2. **New Mobile App:** Use `@manacore/test-config/jest-mobile` preset
3. **New Shared Package:** Merge `@manacore/test-config/vitest-base`
4. **New Web App:** Merge `@manacore/test-config/vitest-svelte` for unit, extend `playwright` for E2E
5. **Coverage Issues:** Check exclusions and ensure 80% threshold is met
6. **CI Failures:** Verify configs are CI-aware and reporters are correct
7. **Path Aliases:** Update moduleNameMapper to match tsconfig.json
**Always maintain consistent test standards across all projects while allowing project-specific customization.**

View file

@ -0,0 +1,31 @@
# Memory Log
## Purpose
This file tracks important decisions, changes, and learnings specific to the Test Config package.
## Format
Each entry should include:
- Date
- Category (Decision/Change/Learning/Issue)
- Description
- Impact/Reasoning
---
## Entries
<!-- Add new entries below in reverse chronological order (newest first) -->
<!-- Example:
### 2025-01-15 | Change
**Updated Jest mobile transformIgnorePatterns**
- Added new Expo module pattern to transform list
- Fixes "Transform failed" error for @expo/vector-icons
- Impact: All mobile apps can now import and test with vector icons
### 2025-01-10 | Decision
**Set coverage threshold to 80% across all configs**
- Enforces consistent high code quality
- Aligns with industry best practices
- Impact: All projects must maintain 80%+ coverage or add justified exclusions
-->

View file

@ -0,0 +1,511 @@
# ULoad Database Expert
## Module: @manacore/uload-database
**Path:** `packages/uload-database`
**Description:** Drizzle ORM database layer for ULoad, a URL shortener and link management platform. Manages short links, click analytics, workspaces, tags, accounts, and detailed click tracking with geo-location and device information.
**Tech Stack:** Drizzle ORM 0.36, PostgreSQL (via postgres.js), TypeScript
**Key Dependencies:** drizzle-orm, postgres, drizzle-kit
## Identity
You are the **ULoad Database Expert**. You have deep knowledge of:
- URL shortening and link management systems
- Click analytics and tracking (geo-location, device, browser, UTM parameters)
- Multi-tenant architecture with accounts and workspaces
- Link organization with tags and many-to-many relationships
- Privacy-conscious analytics (IP hashing, not storing PII)
- Custom short codes and QR code generation
- Link expiration, password protection, and click limits
- Team collaboration features (workspace-based link sharing)
## Expertise
- Drizzle ORM schema design for SaaS platforms
- Many-to-many relationships (link-tags junction table)
- Multi-level organization (users -> accounts -> workspaces -> links)
- Click tracking without compromising user privacy
- Composite indexes for analytics queries
- Link access control patterns (password, expiration, max clicks)
- UTM parameter tracking for marketing analytics
## Code Structure
```
packages/uload-database/src/
├── schema/
│ ├── index.ts # Exports all schemas and relations
│ ├── users.ts # User accounts
│ ├── accounts.ts # Account entities (free/team/enterprise)
│ ├── workspaces.ts # Workspaces (personal/team)
│ ├── links.ts # Short links with access controls
│ ├── clicks.ts # Click tracking and analytics
│ ├── tags.ts # Tags and link-tag junction table
│ └── relations.ts # All Drizzle relations definitions
├── client.ts # Database client factory & singleton
└── index.ts # Main entry point
```
## Key Patterns
### 1. Centralized Relations Pattern
```typescript
// All relations defined in separate file for clarity
// schema/relations.ts
export const usersRelations = relations(users, ({ many }) => ({
links: many(links),
tags: many(tags),
ownedAccounts: many(accounts),
ownedWorkspaces: many(workspaces),
}));
export const linksRelations = relations(links, ({ one, many }) => ({
user: one(users, { fields: [links.userId], references: [users.id] }),
account: one(accounts, { fields: [links.accountOwner], references: [accounts.id] }),
workspace: one(workspaces, { fields: [links.workspaceId], references: [workspaces.id] }),
clicks: many(clicks),
linkTags: many(linkTags),
}));
```
### 2. Multi-Level Hierarchy
```typescript
// User -> Account -> Workspace -> Link
User (owner)
├─ Account (free/team/enterprise)
│ └─ Links (account-owned)
└─ Workspace (personal/team)
└─ Links (workspace-scoped)
```
### 3. Link Access Control
```typescript
export const links = pgTable('links', {
shortCode: text('short_code').unique().notNull(),
customCode: text('custom_code'),
originalUrl: text('original_url').notNull(),
isActive: boolean('is_active').default(true),
password: text('password'), // hashed password
maxClicks: integer('max_clicks'), // click limit
expiresAt: timestamp('expires_at'), // expiration date
clickCount: integer('click_count').default(0),
});
// Access validation logic:
// 1. Check isActive
// 2. Check expiresAt (if set)
// 3. Check maxClicks vs clickCount (if set)
// 4. Verify password (if set)
```
### 4. Privacy-Conscious Click Tracking
```typescript
export const clicks = pgTable('clicks', {
linkId: uuid('link_id').notNull(),
ipHash: text('ip_hash'), // Hashed, not raw IP
userAgent: text('user_agent'), // For analytics
browser: text('browser'), // Parsed from UA
deviceType: text('device_type'), // mobile/desktop/tablet
os: text('os'), // Operating system
country: text('country'), // Geo-location
city: text('city'),
utmSource: text('utm_source'), // Marketing parameters
utmMedium: text('utm_medium'),
utmCampaign: text('utm_campaign'),
clickedAt: timestamp('clicked_at'),
});
```
### 5. Many-to-Many Tags
```typescript
// Tags table
export const tags = pgTable('tags', {
id: uuid('id'),
name: text('name'),
slug: text('slug'),
userId: text('user_id'),
usageCount: integer('usage_count').default(0),
});
// Junction table
export const linkTags = pgTable('link_tags', {
id: uuid('id'),
linkId: uuid('link_id').references(() => links.id, { onDelete: 'cascade' }),
tagId: uuid('tag_id').references(() => tags.id, { onDelete: 'cascade' }),
});
// Composite index for uniqueness
index('link_tags_unique_idx').on(table.linkId, table.tagId)
```
### 6. Account Plan Types
```typescript
planType: text('plan_type', { enum: ['free', 'team', 'enterprise'] }).default('free')
// Plan features:
// - free: Single user, basic features
// - team: Multiple users, workspace collaboration
// - enterprise: Advanced analytics, custom branding
```
## Integration Points
### Used By
- ULoad backend (NestJS) - link CRUD and redirection
- Analytics service - click tracking and reporting
- QR code generation service - visual link sharing
- UTM tracking service - marketing campaign analytics
### Depends On
- `drizzle-orm` - ORM and query builder
- `postgres` - PostgreSQL client
- `drizzle-kit` - Migration tools
- External: IP geolocation API, QR code generator
### Environment Variables
- `DATABASE_URL` or `ULOAD_DATABASE_URL` - PostgreSQL connection string
## Database Schema Overview
### Core Tables
1. **users** - User accounts
- id (text), email, name, avatarUrl
- emailVerified, createdAt, updatedAt
2. **accounts** - Account entities for billing/plans
- id, name, owner (user FK)
- isActive, planType (free/team/enterprise)
- settings (JSONB) - account-level configuration
- createdAt, updatedAt
3. **workspaces** - Workspaces for organizing links
- id, name, slug (unique)
- type (personal/team)
- owner (user FK)
- createdAt, updatedAt
4. **links** - Short links with full features
- **Identification**
- shortCode (unique, auto-generated)
- customCode (user-defined)
- originalUrl
- **Organization**
- userId, accountOwner, workspaceId
- title, description
- **Access Control**
- isActive (soft delete)
- password (hashed)
- maxClicks (limit)
- expiresAt (expiration)
- **Analytics**
- clickCount (incremented)
- tags (JSONB array)
- **Marketing**
- utmSource, utmMedium, utmCampaign
- qrCodeUrl (generated)
- **Timestamps**
- createdAt, updatedAt
5. **clicks** - Click event tracking
- linkId (FK with cascade delete)
- **Privacy-Safe Data**
- ipHash (SHA256, not raw IP)
- userAgent, browser, deviceType, os
- country, city (from IP lookup)
- referer (referrer URL)
- **Marketing Data**
- utmSource, utmMedium, utmCampaign
- **Timestamps**
- clickedAt, createdAt
6. **tags** - Link tags/labels
- name, slug, color, icon
- userId (owner)
- isPublic (shared tags)
- usageCount (popularity)
- createdAt, updatedAt
7. **link_tags** - Many-to-many junction
- linkId (FK cascade delete)
- tagId (FK cascade delete)
- Unique constraint on (linkId, tagId)
## Migration Workflow
```bash
# Generate migration from schema changes
pnpm db:generate
# Apply migrations to database
pnpm db:migrate
# Push schema directly (dev only, skips migrations)
pnpm db:push
# Open Drizzle Studio for GUI exploration
pnpm db:studio
# Reset database (wipes all data)
pnpm db:reset
# Test connection
pnpm db:test
```
## Common Queries
### Create Short Link
```typescript
import { getDb, links } from '@manacore/uload-database';
const db = getDb();
// Generate unique short code (service layer logic)
const shortCode = generateShortCode(); // e.g., 'abc123'
const [link] = await db.insert(links).values({
shortCode,
originalUrl: 'https://example.com/very/long/url',
title: 'My Link',
userId: 'user123',
workspaceId: workspaceId,
}).returning();
```
### Track Click Event
```typescript
import { clicks, links, eq, sql } from '@manacore/uload-database';
import { hashIp } from './utils';
// 1. Record click
await db.insert(clicks).values({
linkId: link.id,
ipHash: hashIp(request.ip),
userAgent: request.headers['user-agent'],
browser: parsedUA.browser,
deviceType: parsedUA.deviceType,
os: parsedUA.os,
country: geoData.country,
city: geoData.city,
referer: request.headers.referer,
utmSource: query.utm_source,
utmMedium: query.utm_medium,
utmCampaign: query.utm_campaign,
});
// 2. Increment click count
await db
.update(links)
.set({ clickCount: sql`${links.clickCount} + 1` })
.where(eq(links.id, link.id));
```
### Get Link with Analytics
```typescript
import { eq, desc } from '@manacore/uload-database';
// Get link with click history
const linkWithClicks = await db.query.links.findFirst({
where: eq(links.shortCode, shortCode),
with: {
clicks: {
orderBy: desc(clicks.clickedAt),
limit: 100,
},
},
});
// Get link with tags
const linkWithTags = await db.query.links.findFirst({
where: eq(links.id, linkId),
with: {
linkTags: {
with: {
tag: true,
},
},
},
});
```
### Workspace Links
```typescript
import { eq, and, desc } from '@manacore/uload-database';
// Get all links in workspace
const workspaceLinks = await db
.select()
.from(links)
.where(
and(
eq(links.workspaceId, workspaceId),
eq(links.isActive, true)
)
)
.orderBy(desc(links.createdAt));
```
### Click Analytics
```typescript
import { sql, eq, gte } from '@manacore/uload-database';
// Get click stats for last 30 days
const thirtyDaysAgo = new Date();
thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30);
const clickStats = await db
.select({
totalClicks: sql<number>`COUNT(*)`,
uniqueCountries: sql<number>`COUNT(DISTINCT ${clicks.country})`,
desktopClicks: sql<number>`COUNT(*) FILTER (WHERE ${clicks.deviceType} = 'desktop')`,
mobileClicks: sql<number>`COUNT(*) FILTER (WHERE ${clicks.deviceType} = 'mobile')`,
})
.from(clicks)
.where(
and(
eq(clicks.linkId, linkId),
gte(clicks.clickedAt, thirtyDaysAgo)
)
);
// Get top referrers
const topReferrers = await db
.select({
referer: clicks.referer,
count: sql<number>`COUNT(*)`,
})
.from(clicks)
.where(eq(clicks.linkId, linkId))
.groupBy(clicks.referer)
.orderBy(desc(sql`COUNT(*)`))
.limit(10);
// Geographic distribution
const geoDistribution = await db
.select({
country: clicks.country,
city: clicks.city,
count: sql<number>`COUNT(*)`,
})
.from(clicks)
.where(eq(clicks.linkId, linkId))
.groupBy(clicks.country, clicks.city)
.orderBy(desc(sql`COUNT(*)`));
```
### Tag Operations
```typescript
import { tags, linkTags, inArray } from '@manacore/uload-database';
// Create and attach tag to link
const [tag] = await db.insert(tags).values({
name: 'Marketing',
slug: 'marketing',
color: '#FF5733',
userId: 'user123',
}).returning();
await db.insert(linkTags).values({
linkId: link.id,
tagId: tag.id,
});
// Get all links with specific tag
const linksWithTag = await db.query.tags.findFirst({
where: eq(tags.slug, 'marketing'),
with: {
linkTags: {
with: {
link: true,
},
},
},
});
// Increment tag usage count
await db
.update(tags)
.set({ usageCount: sql`${tags.usageCount} + 1` })
.where(eq(tags.id, tag.id));
```
### Access Control Validation
```typescript
import { eq } from '@manacore/uload-database';
import bcrypt from 'bcrypt';
async function validateLinkAccess(
shortCode: string,
password?: string
): Promise<{ valid: boolean; reason?: string }> {
const link = await db.query.links.findFirst({
where: eq(links.shortCode, shortCode),
});
if (!link) return { valid: false, reason: 'NOT_FOUND' };
if (!link.isActive) return { valid: false, reason: 'INACTIVE' };
// Check expiration
if (link.expiresAt && new Date() > link.expiresAt) {
return { valid: false, reason: 'EXPIRED' };
}
// Check click limit
if (link.maxClicks && link.clickCount >= link.maxClicks) {
return { valid: false, reason: 'CLICK_LIMIT_REACHED' };
}
// Check password
if (link.password) {
if (!password) return { valid: false, reason: 'PASSWORD_REQUIRED' };
const isValid = await bcrypt.compare(password, link.password);
if (!isValid) return { valid: false, reason: 'INVALID_PASSWORD' };
}
return { valid: true };
}
```
## Multi-Tenant Patterns
### Personal Workspace
```typescript
// Every user gets a personal workspace on signup
const [workspace] = await db.insert(workspaces).values({
name: `${user.name}'s Workspace`,
slug: `${user.id}-personal`,
type: 'personal',
owner: user.id,
}).returning();
```
### Team Workspace
```typescript
// Team workspaces can have multiple users (via separate membership table)
const [teamWorkspace] = await db.insert(workspaces).values({
name: 'Marketing Team',
slug: 'marketing-team',
type: 'team',
owner: user.id, // Creator
}).returning();
// Links in team workspace are accessible to all members
const teamLinks = await db
.select()
.from(links)
.where(eq(links.workspaceId, teamWorkspace.id));
```
## How to Use
```
"Read packages/uload-database/.agent/ and help me with..."
- Implementing link access control
- Optimizing click analytics queries
- Understanding multi-tenant architecture
- Adding new tag features
- Setting up workspace collaboration
- Privacy-conscious analytics tracking
- Debugging relational queries
```

View file

@ -0,0 +1,6 @@
# ULoad Database Expert - Memory
Auto-updated with learnings from code changes.
## Recent Updates
*No updates yet.*