forked from coracle/flotilla
10 KiB
10 KiB
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:
- Third-party libraries first
- Then
lib/imports - Then
app/imports
Example:
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
deriveEventsMappedorderiveProfileetc - 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 (
urlfor spaces,hfor rooms) - Derive all other data inside the component from identifiers
- Example: Don't pass
membersprop, derive it fromhinside component
CRITICAL Code Style Guidelines:
- No
null- only useundefined - 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 tounknown, they are likely because the upstream definition of the data is incorrect. - When dynamically building classes, use
cxfromclassnamesrather than embedded ternaries or svelte 4's oldclass:syntax. - When creating forms, use
FieldInlineorFieldinstead 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] || "", usegetTagValue(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
- Determine if it's generic (
lib/components/) or app-specific (app/components/) - Follow naming convention:
PascalCase.svelte - Import in dependency order (3rd party → lib → app)
- Use stores for state, runes only for UI reactivity
Creating a New Route
- Add to
src/routes/following SvelteKit conventions - Use
+page.sveltefor page component - Use
+layout.sveltefor shared layouts - Top-level sync logic goes in root
+layout.svelte
Loading Data from Network
- Use utilities from
app/core/requests.ts - Or create derived stores in
app/core/state.ts - Use
load,pull, orrequestfrom@welshman/net
Publishing Events
- Create
make*function to build event template - Create
publish*function usingpublishThunk - Display thunk status to user (for cancel/error handling)
- These go in in
app/core/commands.ts
Managing Modals/Toasts
- Import from
app/util/modal.tsorapp/util/toast.ts - Pass component objects with parameters
- Use
$state.snapshotif calling component might unmount
Development Workflow
Agents should not run the dev server or build the app. Instead, use the following commands:
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_depsscript to link local welshman packages - Avoid committing
pnpm.overrideschanges
Git Workflow:
masterbranch auto-deploys to production- Work on feature branches based on
devbranch - 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