Files

269 lines
10 KiB
Markdown

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