web-app/CLAUDE.md
padreug 3679c719a3 Add form implementation standards for Shadcn/UI components with vee-validate
- Introduce critical guidelines for using Shadcn/UI form components in conjunction with vee-validate for form handling.
- Provide detailed examples for required form setup, template structure, and key requirements to ensure proper validation and accessibility.
- Emphasize the importance of type safety using Zod schema for validation and correct form handling practices.

These updates aim to standardize form implementations across the application, enhancing consistency and maintainability.
2025-09-08 16:58:05 +02:00

12 KiB

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):

// 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):

// 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:

// DON'T import services directly - breaks modular architecture
import { relayHubComposable } from '@/composables/useRelayHub'

Always do this:

// 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:

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:

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:

<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:

<!-- 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:

<!-- 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>

Code Conventions:

  • Use TypeScript interfaces over types for extendability
  • Prefer functional and declarative patterns over classes
  • 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

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