CLAUDE.md#

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

Project Overview#

Skypod is an offline-first, peer-to-peer RSS and podcast Progressive Web App (PWA). The application enables users to manage subscriptions and listening history across multiple devices with direct peer-to-peer synchronization (no central server for data).

Key Technologies: TypeScript, Preact, Vite, Dexie.js (IndexedDB), Express, WebRTC (simple-peer), WebSockets, Zod

Development Commands#

Starting Development#

npm run dev

Starts all development services concurrently using wireit:

  • Vite dev server at http://127.0.0.1:4000 (frontend)
  • Backend server at http://127.0.0.1:4001 (WebSocket + API)
  • Live type-checking and linting in watch mode

Running Tests#

npm run test              # Run all tests once
npm run start:tests       # Run tests in watch mode

Tests use Jest with ts-jest. Test files are named *.spec.ts or *.spec.tsx.

To run a single test file:

npx jest src/path/to/file.spec.ts

Linting and Type-Checking#

npm run lint              # Run ESLint
npm run types             # Run TypeScript type-checking
npm run build             # Build production frontend

Production#

npm run start:prod        # Build and run production server

Git Hooks#

Pre-commit hook runs type-checking and linting automatically. Enable with:

git config core.hooksPath .githooks

Architecture#

Module Structure#

The codebase is organized into four main directories with path aliases:

  • #client/* (src/client/) - Preact frontend application
  • #server/* (src/server/) - Node.js Express backend
  • #common/* (src/common/) - Shared code (protocol, crypto, utilities)
  • #skypod/* (src/skypod/) - Domain-specific schemas and actions

These path aliases are configured in both package.json (imports) and tsconfig.json (paths). Always use these aliases for absolute imports.

Frontend Client (src/client/)#

Built with Preact and Vite. Key subdirectories:

  • realm/ - Core P2P connection management
    • service-connection.ts - Main WebSocket connection to signaling server
    • service-connection-peer.ts - WebRTC peer connection management
    • service-connection-sync.ts - Sync protocol coordinator
    • context-connection.tsx - React context for connection state
    • context-identity.tsx - React context for user identity
  • root/ - Database setup (Dexie/IndexedDB)
  • skypod/ - Application-specific components and workers
  • components/ - Reusable UI components

Backend Server (src/server/)#

Lightweight Express server with two primary functions:

  1. WebSocket Signaling Server (routes-socket/) - Enables WebRTC connection establishment between peers
  2. API Proxy (routes-api/) - Fetches and parses external RSS feeds

The server maintains realm state (connected peers) but does NOT store user data - all data lives on clients.

Common Library (src/common/)#

Shared isomorphic code:

  • protocol/ - P2P protocol definitions
    • logical-clock.ts - Hybrid Logical Clock (HLC) implementation for causal ordering
    • messages.ts - Zod schemas for all WebSocket/WebRTC messages
    • brands.ts - Branded types for identity IDs
  • crypto/ - JWT and JWK utilities for authentication
  • async/ - Utilities (semaphore, blocking queue, sleep, etc.)
  • schema/ - Common Zod schemas
  • socket.ts - Shared WebSocket message handling
  • errors.ts - Custom error types (ProtocolError)

Domain Layer (src/skypod/)#

Application-specific schemas and action definitions for the podcast/RSS domain.

Synchronization Protocol#

The P2P sync protocol is the core architectural component. It uses a Hybrid Logical Clock (HLC) for causal ordering of events and follows a specific pattern:

PULL for Catch-up#

When a new client connects or comes online after being offline, it PULLS the complete action history from a single deterministically chosen syncPartner. This efficiently brings the client up to date.

PUSH for Updates#

All clients PUSH their own new or offline-generated changes to all connected peers. This is NOT a simple broadcast - each client sends a tailored set of missing actions to each peer based on a handshake where "knowledge vectors" (last known timestamps) are exchanged.

Key Components#

  • Hybrid Logical Clock (src/common/protocol/logical-clock.ts) - Provides total ordering across distributed events even with clock skew
  • Message Schemas (src/common/protocol/messages.ts) - Defines all protocol messages using Zod
  • Connection Services (src/client/realm/service-connection-*.ts) - Implements the sync protocol

When working on sync-related code, understand that ordering is critical - the HLC timestamp format is lc:seconds:counter:identid where each component is used for deterministic ordering.

Build System#

Uses wireit for dependency-aware task orchestration. All build tasks are defined in package.json under the wireit key.

  • Tasks automatically track file dependencies and outputs
  • Service tasks (prefixed with run:) run continuously in watch mode
  • Compound tasks like start:dev orchestrate multiple services

Testing Conventions#

  • Test files use the pattern *.spec.ts or *.spec.tsx
  • Tests use @jest/globals imports for Jest functions
  • Use #common/*, #client/*, #server/* aliases in tests (configured in jest.config.js)
  • Tests support ESM and handle Preact/JSX via ts-jest

Code Style#

  • ESLint + Prettier enforce consistent style
  • Strict TypeScript with strict: true and noImplicitAny: true
  • Zod for runtime validation and schema definitions
  • JSX configured for Preact (jsxImportSource: "preact")

Important Context for AI Assistance#

  1. Path Aliases Are Critical - Always use #common/*, #client/*, #server/*, #skypod/* for imports, never relative paths across module boundaries

  2. Sync Protocol Is Complex - The HLC-based synchronization is the most intricate part of the codebase. Changes to sync logic require careful consideration of causal ordering and distributed consistency

  3. Client Is Offline-First - All user data lives in IndexedDB via Dexie. The server is stateless regarding user data (only maintains ephemeral realm/peer state)

  4. WebRTC for P2P - Peers communicate directly via WebRTC data channels (using simple-peer). The WebSocket connection to the server is only used for signaling and as a fallback broadcast mechanism

  5. Zod Schemas Define Protocol - All message formats are defined as Zod schemas in src/common/protocol/. These schemas are the single source of truth for protocol messages