## Project Overview Flotilla is a Nostr "relays as groups" community chat client. It implements NIP-29 (relay-based groups) to create Discord-like spaces (servers) and rooms (channels). **Tech Stack:** - SvelteKit 5.48+ with TypeScript 5.9+ - Capacitor for cross-platform (Web/PWA, Android, iOS) - TailwindCSS + DaisyUI for styling - Welshman library suite for Nostr protocol - IndexedDB for local storage - Vite for building **Key Concepts:** - **Spaces** - Relays used as community groups (like Discord servers) - **Rooms** - NIP-29 groups within spaces (like Discord channels), identified by `h` - **Chats** - Direct message conversations (NIP-04/NIP-44 encrypted) ## Architecture & Dependency Graph The project follows a **strict acyclic dependency hierarchy**: ``` routes/ (top layer - can depend on anything) ↓ app/components/ (can depend on app/* and lib/*) ↓ app/core/ & app/util/ (can only depend on lib/*) ↓ lib/ (can only depend on external libraries) ↓ external libraries (bottom layer) ``` **Import Ordering Convention (CRITICAL):** Always sort imports by dependency level: 1. Third-party libraries first 2. Then `lib/` imports 3. Then `app/` imports Example: ```typescript import {derived} from "svelte/store" import {throttle} from "throttle-debounce" import {Dialog} from "$lib/components" import {repository} from "$app/core/state" ``` ## File Structure ``` src/ ├── lib/ # Generic reusable code │ ├── components/ # 38 UI components (Button, Dialog, etc.) │ ├── html.ts # DOM utilities │ ├── indexeddb.ts # IndexedDB helpers │ └── util.ts # Generic utilities │ ├── app/ │ ├── core/ │ │ ├── state.ts # State management, stores, constants (687 lines) │ │ ├── commands.ts # Publishing events and other write operations (440+ lines) │ │ ├── requests.ts # Loading data from network (191 lines) │ │ ├── sync.ts # Data synchronization (296 lines) │ │ └── storage.ts # IndexedDB setup │ │ │ ├── util/ │ │ ├── notifications.ts # Push notifications (731 lines) │ │ ├── policies.ts # Relay policies │ │ ├── routes.ts # Routing helpers │ │ ├── modal.ts # Modal management │ │ ├── toast.ts # Toast notifications │ │ ├── theme.ts # Theme switching │ │ └── keyboard.ts # Keyboard handling │ │ │ ├── editor/ # Rich text editor config │ │ ├── index.ts # TipTap setup with Nostr integration │ │ ├── EditorContent.svelte │ │ └── MentionNodeView.ts │ │ │ └── components/ # 188 app-specific components │ ├── Space*.svelte # Space/relay management │ ├── Room*.svelte # Room/channel management │ ├── Chat*.svelte # Direct messaging │ ├── Profile*.svelte # User profiles │ ├── Thread*.svelte # Threaded posts │ └── ... │ ├── routes/ # SvelteKit file-based routing │ ├── +layout.svelte # Root layout (sync logic here) │ ├── spaces/ # Space management │ │ └── [relay]/ # Specific space │ │ ├── chat/ # Space chat │ │ ├── threads/ # Thread posts │ │ ├── calendar/ # Events │ │ └── [h]/ # Specific room (h = room id) │ ├── chat/ # Direct messages │ ├── settings/ # User settings │ └── [bech32]/ # Bech32 entity viewer │ ├── assets/icons/ # ~1,277 SVG icons ├── app.html # HTML template ├── app.css # Global styles └── types.d.ts # Type definitions ``` ## State Management **Core Principles:** - Use Svelte 4 **stores** for all state (NOT runes outside UI components) - Most global state flows through Welshman's `repository` (unidirectional) - Query state using `deriveEventsMapped` or `deriveProfile` etc - Update state by publishing events via `publishThunk` **Thunks:** - Reduce UI latency by handling signatures and sending in background - Return status that should be displayed to user - Allow cancellation and error handling - Immediately publish to local repository for optimistic updates ## Nostr Integration **Welshman Library Suite:** - `@welshman/app` - High-level state (pubkey, signer, repository, tracker) - `@welshman/net` - Network layer (Pool, Socket, load, pull, request) - `@welshman/store` - Svelte integration (deriveEventsMapped, etc.) - `@welshman/util` - Event utilities (kinds, tags, validation) - `@welshman/signer` - Signing abstraction (NIP-01, NIP-07, NIP-46) - `@welshman/router` - Relay routing (inbox/outbox model) - `@welshman/editor` - Rich text editor with Nostr - `@welshman/content` - Content parsing - `@welshman/feeds` - Feed management **Key NIPs Implemented:** - NIP-01: Basic protocol - NIP-44/59/17: Encrypted DMs - NIP-07: Browser extension signing - NIP-19: Bech32 encoding - NIP-29: Relay-based Groups - NIP-42: Relay authentication - NIP-43: Relay membership - NIP-46: Nostr Connect (remote signing) - NIP-57: Lightning Zaps ## Development Conventions **Component Parameterization:** - Only pass entity identifiers (`url` for spaces, `h` for rooms) - Derive all other data inside the component from identifiers - Example: Don't pass `members` prop, derive it from `h` inside component **CRITICAL Code Style Guidelines:** - **No `null`** - only use `undefined` - Svelte 5 runes (`$state`, `$derived`, `$effect`) only in UI components - TailwindCSS and DaisyUI styling - Only add comments for really weird stuff - Do not call functions in components unless a parameter is reactive. Instead, use a svelte store or rune to make it reactive. - Do not use `any`. If there are type errors related to `unknown`, they are likely because the upstream definition of the data is incorrect. - When dynamically building classes, use `cx` from `classnames` rather than embedded ternaries or svelte 4's old `class:` syntax. - When creating forms, use `FieldInline` or `Field` instead of custom elements/tailwindcss - Do not define svelte event handlers inline, instead name them and put them in the script section of templates - Avoid using `as`, except where necessary. Instead, annotate function parameters, and ensure upstream values are typed correctly. - Instead of `getTag(tagName, event.tags)?.[1] || ""`, use `getTagValue(tagName, event.tags)` **Human-First Simplicity (Jon Staab Style):** - Prefer direct, readable code over layered abstractions. - Do not add indirection (extra helpers, wrappers, stores, or derived state) unless it removes real repeated complexity. - Reuse existing Welshman and Flotilla primitives before introducing new utilities or dependencies. - Favor linear control flow and explicit naming over clever patterns. - Remove defensive checks that do not apply in this runtime model. - When two approaches work, pick the one that feels more human and easier to maintain. ## Common Tasks ### Adding a New Component 1. Determine if it's generic (`lib/components/`) or app-specific (`app/components/`) 2. Follow naming convention: `PascalCase.svelte` 3. Import in dependency order (3rd party → lib → app) 4. Use stores for state, runes only for UI reactivity ### Creating a New Route 1. Add to `src/routes/` following SvelteKit conventions 2. Use `+page.svelte` for page component 3. Use `+layout.svelte` for shared layouts 4. Top-level sync logic goes in root `+layout.svelte` ### Loading Data from Network 1. Use utilities from `app/core/requests.ts` 2. Or create derived stores in `app/core/state.ts` 3. Use `load`, `pull`, or `request` from `@welshman/net` ### Publishing Events 1. Create `make*` function to build event template 2. Create `publish*` function using `publishThunk` 3. Display thunk status to user (for cancel/error handling) 4. These go in in `app/core/commands.ts` ### Managing Modals/Toasts - Import from `app/util/modal.ts` or `app/util/toast.ts` - Pass component objects with parameters - Use `$state.snapshot` if calling component might unmount ## Development Workflow Agents should not run the dev server or build the app. Instead, use the following commands: ```bash pnpm run format # Format changed files pnpm run lint # Check formatting and linting pnpm run check # Type check ``` **Welshman Development:** - Clone welshman to parent directory - Use `./link_deps` script to link local welshman packages - Avoid committing `pnpm.overrides` changes **Git Workflow:** - `master` branch auto-deploys to production - Work on feature branches based on `dev` branch - Pre-commit hooks run lint/typecheck automatically ## Environment Variables See `.env.template` for all options. ## Important Files to Reference - **src/app/core/state.ts** - All stores and constants - **src/app/core/sync.ts** - Data synchronization - **src/app/core/requests.ts** - Utilities for requesting data - **src/app/core/commands.ts** - Publishing patterns - **src/app/util/notifications.ts** - Notification badges and push notifications - **src/routes/+layout.svelte** - Top-level sync logic ## Mobile Development **Capacitor Integration:** - Android: Full support, APK builds via `pnpm run release:android` - iOS: Full support (zaps disabled due to App Store policy) - PWA: Progressive Web App with service worker **Native Features:** - Push notifications (FCM/APNs) - Deep linking (nostr: and https: URLs) - Native signing plugin - Keyboard management - Safe area handling - Badge management