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 managementservice-connection.ts- Main WebSocket connection to signaling serverservice-connection-peer.ts- WebRTC peer connection managementservice-connection-sync.ts- Sync protocol coordinatorcontext-connection.tsx- React context for connection statecontext-identity.tsx- React context for user identity
root/- Database setup (Dexie/IndexedDB)skypod/- Application-specific components and workerscomponents/- Reusable UI components
Backend Server (src/server/)#
Lightweight Express server with two primary functions:
- WebSocket Signaling Server (
routes-socket/) - Enables WebRTC connection establishment between peers - 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 definitionslogical-clock.ts- Hybrid Logical Clock (HLC) implementation for causal orderingmessages.ts- Zod schemas for all WebSocket/WebRTC messagesbrands.ts- Branded types for identity IDs
crypto/- JWT and JWK utilities for authenticationasync/- Utilities (semaphore, blocking queue, sleep, etc.)schema/- Common Zod schemassocket.ts- Shared WebSocket message handlingerrors.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:devorchestrate multiple services
Testing Conventions#
- Test files use the pattern
*.spec.tsor*.spec.tsx - Tests use
@jest/globalsimports for Jest functions - Use
#common/*,#client/*,#server/*aliases in tests (configured injest.config.js) - Tests support ESM and handle Preact/JSX via ts-jest
Code Style#
- ESLint + Prettier enforce consistent style
- Strict TypeScript with
strict: trueandnoImplicitAny: true - Zod for runtime validation and schema definitions
- JSX configured for Preact (
jsxImportSource: "preact")
Important Context for AI Assistance#
-
Path Aliases Are Critical - Always use
#common/*,#client/*,#server/*,#skypod/*for imports, never relative paths across module boundaries -
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
-
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)
-
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
-
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