471 lines
No EOL
17 KiB
Markdown
471 lines
No EOL
17 KiB
Markdown
# CLAUDE.md
|
|
|
|
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
|
|
|
## Development Commands
|
|
|
|
**Development**
|
|
- `npm run dev` - Start development server with Vite (includes --host flag)
|
|
- `npm run build` - Build for production (includes TypeScript check with vue-tsc -b)
|
|
- `npm run preview` - Preview production build locally
|
|
- `npm run analyze` - Build with bundle analysis (opens visualization)
|
|
|
|
**Electron Development**
|
|
- `npm run electron:dev` - Run both Vite dev server and Electron concurrently
|
|
- `npm run electron:build` - Full build and package for Electron
|
|
- `npm run start` - Start Electron using Forge
|
|
- `npm run package` - Package Electron app with Forge
|
|
- `npm run make` - Create distributables with Electron Forge
|
|
|
|
## Architecture Overview
|
|
|
|
This is a modular Vue 3 + TypeScript + Vite application with Electron support, featuring a Nostr protocol client and Lightning Network integration for events/ticketing.
|
|
|
|
### **Modular Architecture**
|
|
|
|
The application uses a plugin-based modular architecture with dependency injection for service management:
|
|
|
|
**Core Modules:**
|
|
- **Base Module** (`src/modules/base/`) - Core infrastructure (Nostr, Auth, PWA)
|
|
- **Nostr Feed Module** (`src/modules/nostr-feed/`) - Social feed functionality
|
|
- **Chat Module** (`src/modules/chat/`) - Encrypted Nostr chat
|
|
- **Events Module** (`src/modules/events/`) - Event ticketing with Lightning payments
|
|
- **Market Module** (`src/modules/market/`) - Nostr marketplace functionality
|
|
|
|
**Module Configuration:**
|
|
- Modules are configured in `src/app.config.ts`
|
|
- Each module can be enabled/disabled and configured independently
|
|
- Modules have dependencies (e.g., all modules depend on 'base')
|
|
|
|
**Plugin Manager:**
|
|
- `src/core/plugin-manager.ts` handles module lifecycle
|
|
- Registers, installs, and manages module dependencies
|
|
- Handles route registration from modules
|
|
|
|
### **Dependency Injection Pattern**
|
|
|
|
**CRITICAL**: Always use the dependency injection pattern for accessing shared services:
|
|
|
|
**Service Registration (Base Module):**
|
|
```typescript
|
|
// src/modules/base/index.ts
|
|
import { container, SERVICE_TOKENS } from '@/core/di-container'
|
|
|
|
container.provide(SERVICE_TOKENS.RELAY_HUB, relayHub)
|
|
container.provide(SERVICE_TOKENS.AUTH_SERVICE, auth)
|
|
```
|
|
|
|
**Service Consumption (Other Modules):**
|
|
```typescript
|
|
// In any module's composables or services
|
|
import { injectService, SERVICE_TOKENS } from '@/core/di-container'
|
|
|
|
const relayHub = injectService(SERVICE_TOKENS.RELAY_HUB)
|
|
const authService = injectService(SERVICE_TOKENS.AUTH_SERVICE)
|
|
```
|
|
|
|
**❌ NEVER do this:**
|
|
```typescript
|
|
// DON'T import services directly - breaks modular architecture
|
|
import { relayHubComposable } from '@/composables/useRelayHub'
|
|
```
|
|
|
|
**✅ Always do this:**
|
|
```typescript
|
|
// DO use dependency injection for loose coupling
|
|
const relayHub = injectService(SERVICE_TOKENS.RELAY_HUB)
|
|
```
|
|
|
|
**Available Services:**
|
|
- `SERVICE_TOKENS.RELAY_HUB` - Centralized Nostr relay management
|
|
- `SERVICE_TOKENS.AUTH_SERVICE` - Authentication services
|
|
- `SERVICE_TOKENS.VISIBILITY_SERVICE` - App visibility and connection management
|
|
|
|
**Core Stack:**
|
|
- Vue 3 with Composition API (`<script setup>` style)
|
|
- TypeScript throughout
|
|
- Vite build system with PWA support
|
|
- Electron for desktop app packaging
|
|
- Pinia for state management
|
|
- Vue Router for navigation
|
|
- TailwindCSS v4 with Shadcn/ui components
|
|
- Vue-i18n for internationalization
|
|
|
|
**Key Features:**
|
|
- Nostr protocol client for decentralized social networking
|
|
- Lightning Network integration for event ticketing
|
|
- PWA capabilities with service worker
|
|
- Theme switching and language switching
|
|
- Real-time connection status monitoring
|
|
|
|
**Directory Structure:**
|
|
- `src/components/` - Vue components organized by feature
|
|
- `ui/` - Shadcn/ui component library
|
|
- `nostr/` - Nostr-specific components
|
|
- `events/` - Event/ticketing components
|
|
- `layout/` - App layout components
|
|
- `src/composables/` - Vue composables for reusable logic
|
|
- `src/stores/` - Pinia stores for state management
|
|
- `src/lib/` - Core business logic
|
|
- `nostr/` - Nostr client implementation
|
|
- `api/` - API integrations
|
|
- `types/` - TypeScript type definitions
|
|
- `src/pages/` - Route pages
|
|
- `electron/` - Electron main process code
|
|
|
|
**Nostr Integration:**
|
|
The app connects to Nostr relays using a custom NostrClient class built on nostr-tools. Key files:
|
|
- `src/lib/nostr/client.ts` - Core Nostr client implementation
|
|
- `src/composables/useNostr.ts` - Vue composable for Nostr connection management
|
|
- `src/stores/nostr.ts` - Pinia store for Nostr state
|
|
|
|
## Development Guidelines
|
|
|
|
### **Modular Architecture Patterns**
|
|
|
|
**Module Structure:**
|
|
```
|
|
src/modules/[module-name]/
|
|
├── index.ts # Module plugin definition
|
|
├── components/ # Module-specific components
|
|
├── composables/ # Module composables
|
|
├── services/ # Module services
|
|
├── stores/ # Module-specific stores
|
|
├── types/ # Module type definitions
|
|
└── views/ # Module pages/views
|
|
```
|
|
|
|
**Module Plugin Pattern:**
|
|
```typescript
|
|
export const myModule: ModulePlugin = {
|
|
name: 'my-module',
|
|
version: '1.0.0',
|
|
dependencies: ['base'], // Always depend on base for core services
|
|
|
|
async install(app: App, options?: { config?: MyModuleConfig }) {
|
|
// Module installation logic
|
|
// Register components, initialize services, etc.
|
|
},
|
|
|
|
routes: [/* module routes */],
|
|
components: {/* exported components */},
|
|
composables: {/* exported composables */}
|
|
}
|
|
```
|
|
|
|
**Service Integration:**
|
|
- All modules MUST use dependency injection for shared services
|
|
- NEVER import services directly across module boundaries
|
|
- Base module provides core infrastructure services
|
|
- Modules can register their own services in the DI container
|
|
|
|
**⚠️ CRITICAL - WebSocket Connection Management:**
|
|
- **ALWAYS integrate with VisibilityService for any module that uses WebSocket connections**
|
|
- All services extending `BaseService` have automatic access to `this.visibilityService`
|
|
- Register visibility callbacks during service initialization: `this.visibilityService.registerService(name, onResume, onPause)`
|
|
- Implement proper connection recovery in `onResume()` handler (check health, reconnect if needed, restore subscriptions)
|
|
- Implement battery-conscious pausing in `onPause()` handler (stop heartbeats, queue operations)
|
|
- **Mobile browsers suspend WebSocket connections when app loses visibility** - visibility management is essential for reliable real-time features
|
|
- See `docs/VisibilityService.md` and `docs/VisibilityService-Integration.md` for comprehensive integration guides
|
|
- Future modules will likely ALL depend on WebSocket connections - plan for visibility management from the start
|
|
|
|
### **Centralized Infrastructure**
|
|
|
|
**Nostr Relay Management:**
|
|
- Single RelayHub manages all Nostr connections
|
|
- All modules use the same relay configuration from `VITE_NOSTR_RELAYS`
|
|
- No module should create separate relay connections
|
|
|
|
**Authentication:**
|
|
- Centralized auth service handles all authentication
|
|
- Modules access auth state through dependency injection
|
|
- Router guards use the shared auth service
|
|
|
|
**Configuration:**
|
|
- Environment variables prefixed with `VITE_`
|
|
- Module configs in `src/app.config.ts`
|
|
- Centralized config parsing and validation
|
|
|
|
### **Form Implementation Standards**
|
|
|
|
**CRITICAL: Always use Shadcn/UI Form Components with vee-validate**
|
|
|
|
All forms in the application MUST follow the official Shadcn Vue form implementation pattern:
|
|
|
|
**Required Form Setup:**
|
|
```typescript
|
|
import { useForm } from 'vee-validate'
|
|
import { toTypedSchema } from '@vee-validate/zod'
|
|
import * as z from 'zod'
|
|
import {
|
|
FormControl,
|
|
FormDescription,
|
|
FormField,
|
|
FormItem,
|
|
FormLabel,
|
|
FormMessage,
|
|
} from '@/components/ui/form'
|
|
|
|
// 1. Define Zod schema for validation
|
|
const formSchema = toTypedSchema(z.object({
|
|
name: z.string().min(1, "Name is required").max(100, "Name too long"),
|
|
email: z.string().email("Invalid email address").optional(),
|
|
items: z.array(z.string()).min(1, "Select at least one item"),
|
|
}))
|
|
|
|
// 2. Set up form with vee-validate
|
|
const form = useForm({
|
|
validationSchema: formSchema,
|
|
initialValues: {
|
|
name: '',
|
|
email: '',
|
|
items: []
|
|
}
|
|
})
|
|
|
|
// 3. Destructure form methods
|
|
const { setFieldValue, resetForm, values, meta } = form
|
|
|
|
// 4. Create form validation computed
|
|
const isFormValid = computed(() => meta.value.valid)
|
|
|
|
// 5. Create submit handler with form.handleSubmit
|
|
const onSubmit = form.handleSubmit(async (values) => {
|
|
console.log('Form submitted:', values)
|
|
// Handle form submission logic
|
|
})
|
|
```
|
|
|
|
**Required Form Template Structure:**
|
|
```vue
|
|
<template>
|
|
<!-- form.handleSubmit automatically prevents default submission -->
|
|
<form @submit="onSubmit" class="space-y-6">
|
|
<!-- Text Input Field -->
|
|
<FormField v-slot="{ componentField }" name="name">
|
|
<FormItem>
|
|
<FormLabel>Name *</FormLabel>
|
|
<FormControl>
|
|
<Input
|
|
placeholder="Enter name"
|
|
v-bind="componentField"
|
|
/>
|
|
</FormControl>
|
|
<FormDescription>Enter your full name</FormDescription>
|
|
<FormMessage />
|
|
</FormItem>
|
|
</FormField>
|
|
|
|
<!-- Multiple Checkbox Selection -->
|
|
<FormField name="items">
|
|
<FormItem>
|
|
<div class="mb-4">
|
|
<FormLabel class="text-base">Items *</FormLabel>
|
|
<FormDescription>Select one or more items</FormDescription>
|
|
</div>
|
|
<div v-for="item in availableItems" :key="item.id">
|
|
<FormField
|
|
v-slot="{ value, handleChange }"
|
|
type="checkbox"
|
|
:value="item.id"
|
|
:unchecked-value="false"
|
|
name="items"
|
|
>
|
|
<FormItem class="flex flex-row items-start space-x-3 space-y-0">
|
|
<FormControl>
|
|
<Checkbox
|
|
:model-value="value.includes(item.id)"
|
|
@update:model-value="handleChange"
|
|
/>
|
|
</FormControl>
|
|
<FormLabel class="font-normal">{{ item.name }}</FormLabel>
|
|
</FormItem>
|
|
</FormField>
|
|
</div>
|
|
<FormMessage />
|
|
</FormItem>
|
|
</FormField>
|
|
|
|
<!-- Submit Button -->
|
|
<Button
|
|
type="submit"
|
|
:disabled="isLoading || !isFormValid"
|
|
>
|
|
{{ isLoading ? 'Submitting...' : 'Submit' }}
|
|
</Button>
|
|
</form>
|
|
</template>
|
|
```
|
|
|
|
**Key Form Requirements:**
|
|
- ✅ **Form validation**: Use `@submit="onSubmit"` - form.handleSubmit automatically prevents page refresh
|
|
- ✅ **Button state**: Disable submit button with `!isFormValid` until all required fields are valid
|
|
- ✅ **Error display**: Use `<FormMessage />` for automatic error display
|
|
- ✅ **Field binding**: Use `v-bind="componentField"` for proper form field integration
|
|
- ✅ **Checkbox arrays**: Use nested FormField pattern for multiple checkbox selection
|
|
- ✅ **Type safety**: Zod schema provides full TypeScript type safety
|
|
|
|
**❌ NEVER do this:**
|
|
```vue
|
|
<!-- Wrong: Manual form handling without vee-validate -->
|
|
<form @submit.prevent="handleSubmit">
|
|
|
|
<!-- Wrong: Direct v-model bypasses form validation -->
|
|
<Input v-model="myValue" />
|
|
|
|
<!-- Wrong: Manual validation instead of using meta.valid -->
|
|
<Button :disabled="!name || !email">Submit</Button>
|
|
```
|
|
|
|
**✅ ALWAYS do this:**
|
|
```vue
|
|
<!-- Correct: Uses form.handleSubmit for proper form handling -->
|
|
<form @submit="onSubmit">
|
|
|
|
<!-- Correct: Uses FormField with componentField binding -->
|
|
<FormField v-slot="{ componentField }" name="fieldName">
|
|
<FormControl>
|
|
<Input v-bind="componentField" />
|
|
</FormControl>
|
|
</FormField>
|
|
|
|
<!-- Correct: Uses form meta for validation state -->
|
|
<Button :disabled="!isFormValid">Submit</Button>
|
|
```
|
|
|
|
### **Module Development Best Practices**
|
|
|
|
**Module Structure Requirements:**
|
|
1. **Independence**: Modules must be independent of each other - no direct imports between modules
|
|
2. **Base Dependency**: All modules should depend on 'base' module for core infrastructure
|
|
3. **Service Pattern**: All services should extend `BaseService` for standardized initialization
|
|
4. **API Isolation**: Module-specific API calls must be in the module's services folder
|
|
5. **Dependency Injection**: Cross-module communication only through DI container
|
|
|
|
**Required Module Structure:**
|
|
```
|
|
src/modules/[module-name]/
|
|
├── index.ts # Module plugin definition (REQUIRED)
|
|
├── components/ # Module-specific components
|
|
├── composables/ # Module composables (use DI for services)
|
|
├── services/ # Module services (extend BaseService)
|
|
│ ├── [module]Service.ts # Core module service
|
|
│ └── [module]API.ts # LNbits API integration
|
|
├── stores/ # Module-specific Pinia stores
|
|
├── types/ # Module type definitions
|
|
└── views/ # Module pages/views
|
|
```
|
|
|
|
**Service Implementation Pattern:**
|
|
```typescript
|
|
// Always extend BaseService for services
|
|
export class MyModuleService extends BaseService {
|
|
protected readonly metadata = {
|
|
name: 'MyModuleService',
|
|
version: '1.0.0',
|
|
dependencies: [] // List service dependencies
|
|
}
|
|
|
|
protected async onInitialize(): Promise<void> {
|
|
// Service-specific initialization
|
|
}
|
|
}
|
|
|
|
// API services for LNbits integration
|
|
export class MyModuleAPI extends BaseService {
|
|
private baseUrl: string
|
|
|
|
constructor() {
|
|
super()
|
|
const config = appConfig.modules.myModule.config
|
|
this.baseUrl = config.apiConfig.baseUrl
|
|
}
|
|
|
|
// API methods here
|
|
}
|
|
```
|
|
|
|
**Module Plugin Pattern:**
|
|
```typescript
|
|
export const myModule: ModulePlugin = {
|
|
name: 'my-module',
|
|
version: '1.0.0',
|
|
dependencies: ['base'], // Always depend on base
|
|
|
|
async install(app: App, options?: { config?: MyModuleConfig }) {
|
|
// 1. Create service instances
|
|
const myService = new MyModuleService()
|
|
const myAPI = new MyModuleAPI()
|
|
|
|
// 2. Register in DI container
|
|
container.provide(SERVICE_TOKENS.MY_SERVICE, myService)
|
|
container.provide(SERVICE_TOKENS.MY_API, myAPI)
|
|
|
|
// 3. Initialize services
|
|
await myService.initialize({
|
|
waitForDependencies: true,
|
|
maxRetries: 3
|
|
})
|
|
|
|
// 4. Register components if needed
|
|
app.component('MyComponent', MyComponent)
|
|
}
|
|
}
|
|
```
|
|
|
|
**Nostr Integration Rules:**
|
|
1. **NEVER create separate relay connections** - always use the central RelayHub
|
|
2. **Access RelayHub through DI**: `const relayHub = injectService(SERVICE_TOKENS.RELAY_HUB)`
|
|
3. **Use RelayHub methods** for all Nostr operations (subscribe, publish, etc.)
|
|
4. **Event kinds** should be module-specific and follow NIP specifications
|
|
|
|
**LNbits API Integration:**
|
|
1. **Create module-specific API service** in `services/[module]API.ts`
|
|
2. **Extend BaseService** for automatic dependency management
|
|
3. **Use authentication headers**: `X-Api-Key: walletKey`
|
|
4. **Base URL from config**: Use `appConfig.modules.[module].config.apiConfig.baseUrl`
|
|
5. **Error handling**: Implement proper error handling with user feedback
|
|
|
|
**Composables Best Practices:**
|
|
```typescript
|
|
export function useMyModule() {
|
|
// Always use DI for services
|
|
const relayHub = injectService(SERVICE_TOKENS.RELAY_HUB)
|
|
const authService = injectService(SERVICE_TOKENS.AUTH_SERVICE)
|
|
const myAPI = injectService(SERVICE_TOKENS.MY_API)
|
|
|
|
// Never import services directly
|
|
// ❌ import { relayHub } from '@/modules/base/nostr/relay-hub'
|
|
// ✅ const relayHub = injectService(SERVICE_TOKENS.RELAY_HUB)
|
|
}
|
|
```
|
|
|
|
**WebSocket & Visibility Management:**
|
|
- Services with WebSocket connections MUST integrate with VisibilityService
|
|
- Register visibility callbacks: `this.visibilityService.registerService(name, onResume, onPause)`
|
|
- Handle connection recovery in `onResume()` callback
|
|
- Implement battery-conscious pausing in `onPause()` callback
|
|
|
|
### **Code Conventions:**
|
|
- Use TypeScript interfaces over types for extendability
|
|
- Prefer functional and declarative patterns over classes (except for services)
|
|
- Use Vue Composition API with `<script setup>` syntax
|
|
- Follow naming convention: lowercase-with-dashes for directories
|
|
- Leverage VueUse functions for enhanced reactivity
|
|
- Implement lazy loading for non-critical components
|
|
- Optimize images using WebP format with lazy loading
|
|
- **ALWAYS use dependency injection for cross-module service access**
|
|
- **ALWAYS use Shadcn Form components for all form implementations**
|
|
- **ALWAYS extend BaseService for module services**
|
|
- **NEVER create direct dependencies between modules**
|
|
|
|
**Build Configuration:**
|
|
- Vite config includes PWA, image optimization, and bundle analysis
|
|
- Manual chunking strategy for vendor libraries (vue-vendor, ui-vendor, shadcn)
|
|
- Electron Forge configured for cross-platform packaging
|
|
- TailwindCSS v4 integration via Vite plugin
|
|
|
|
**Environment:**
|
|
- Nostr relay configuration via `VITE_NOSTR_RELAYS` environment variable
|
|
- PWA manifest configured for standalone app experience
|
|
- Service worker with automatic updates every hour |