363 lines
12 KiB
Markdown
363 lines
12 KiB
Markdown
# AGENTS.md
|
|
|
|
This document is designed to help AI Agents better understand and modify the Jumble project.
|
|
|
|
## Project Overview
|
|
|
|
Jumble is a user-friendly Nostr client for exploring relay feeds.
|
|
|
|
- **Project Name**: Jumble
|
|
- **Main Tech Stack**: React 18 + TypeScript + Vite
|
|
- **UI Framework**: Tailwind CSS + Radix UI
|
|
- **State Management**: Jotai
|
|
- **Core Protocol**: Nostr (using nostr-tools)
|
|
|
|
## Technical Architecture
|
|
|
|
### Core Dependencies
|
|
|
|
- **Build Tool**: Vite 5.x
|
|
- **Frontend Framework**: React 18.3.x + TypeScript
|
|
- **Styling Solution**:
|
|
- Tailwind CSS (primary styling framework)
|
|
- Radix UI (unstyled component library)
|
|
- next-themes (theme management)
|
|
- tailwindcss-animate (animations)
|
|
- **State Management**: Jotai 2.x
|
|
- **Routing**: path-to-regexp (custom routing solution)
|
|
- **Rich Text Editor**: TipTap 2.x
|
|
- **Nostr Protocol**: nostr-tools 2.x
|
|
- **Other Key Libraries**:
|
|
- i18next (internationalization)
|
|
- dayjs (date handling)
|
|
- flexsearch (search)
|
|
- qr-code-styling (QR codes)
|
|
- yet-another-react-lightbox (image viewer)
|
|
|
|
### Project Structure
|
|
|
|
```
|
|
jumble/
|
|
├── src/
|
|
│ ├── components/ # React components
|
|
│ │ ├── ui/ # Base UI components (shadcn/ui style)
|
|
│ │ └── ... # Other feature components
|
|
│ ├── providers/ # React Context Providers
|
|
│ ├── services/ # Business logic service layer
|
|
│ ├── hooks/ # Custom React Hooks
|
|
│ ├── lib/ # Utility functions and libraries
|
|
│ ├── types/ # TypeScript type definitions
|
|
│ ├── pages/ # Page components
|
|
| | ├── primary # Primary page components (Left column)
|
|
│ │ └── secondary # secondary page components (Right column)
|
|
│ ├── layouts/ # Layout components
|
|
│ ├── i18n/ # Internationalization resources
|
|
| | ├── locales # Localization files
|
|
│ │ └── index.tx # Basic i18n setup
|
|
│ ├── assets/ # Static assets
|
|
│ ├── App.tsx # App root component
|
|
│ ├── PageManager.tsx # Page manager (custom routing logic)
|
|
│ ├── routes # Route configuration
|
|
| | ├── primary.tsx # Primary routes (Left column)
|
|
│ │ └── secondary.tsx # Secondary routes (Right column)
|
|
│ └── constants.ts # Constants definition
|
|
├── public/ # Public static assets
|
|
└── resources/ # Design resources
|
|
```
|
|
|
|
## Development Guide
|
|
|
|
### Environment Setup
|
|
|
|
### Environment Setup
|
|
|
|
```bash
|
|
# Install dependencies
|
|
npm install
|
|
|
|
# Start development server
|
|
npm run dev
|
|
|
|
# Build for production
|
|
npm run build
|
|
|
|
# Lint code
|
|
npm run lint
|
|
|
|
# Format code
|
|
npm run format
|
|
```
|
|
|
|
## Code Conventions
|
|
|
|
### Component Development
|
|
|
|
1. **Component Structure**: Each major feature component is typically in its own folder, containing index.tsx and related sub-components
|
|
2. **Styling**: Use Tailwind CSS utility classes, complex components can use class-variance-authority (cva)
|
|
3. **Type Safety**: All components should have explicit TypeScript type definitions
|
|
4. **State Management**:
|
|
- Use Jotai atoms for global state management
|
|
- Use Context Providers for cross-component data
|
|
|
|
### Service Layer (Services)
|
|
|
|
Service files located in `src/services/` encapsulate business logic:
|
|
|
|
- `client.service.ts` - Nostr client core logic for fetching and publishing events
|
|
- `indexed-db.service.ts` - IndexedDB data storage
|
|
- `local-storage.service.ts` - LocalStorage management
|
|
- `media-upload.service.ts` - Media upload service
|
|
- `translation.service.ts` - Translation service
|
|
- `lightning.service.ts` - Lightning Network integration
|
|
- `relay-info.service.ts` - Relay information management
|
|
- `blossom.service.ts` - Blossom integration
|
|
- `custom-emoji.service.ts` - Custom emoji management
|
|
- `libre-translate.service.ts` - LibreTranslate API integration
|
|
- `media-manager.service.ts` - Managing media play state
|
|
- `modal-manager.service.ts` - Managing modal stack for back navigation (ensures modals close one by one before actual page navigation)
|
|
- `note-stats.service.ts` - Note statistics storage and retrieval (likes, zaps, reposts)
|
|
- `poll-results.service.ts` - Poll results storage and retrieval
|
|
- `post-editor-cache.service.ts` - Caching post editor content to prevent data loss
|
|
- `web.push.service.ts` - Web metadata fetching for link previews
|
|
|
|
### Providers Architecture
|
|
|
|
The app uses a multi-layered Provider nesting structure (see `App.tsx`):
|
|
|
|
```
|
|
ScreenSizeProvider
|
|
└─ UserPreferencesProvider
|
|
└─ ThemeProvider
|
|
└─ ContentPolicyProvider
|
|
└─ NostrProvider
|
|
└─ ... (more providers)
|
|
```
|
|
|
|
Pay attention to Provider dependencies when modifying functionality.
|
|
|
|
And some Providers are placed in `PageManager.tsx` because they need to use the `usePrimaryPage` and `useSecondaryPage` hooks.
|
|
|
|
### Routing System
|
|
|
|
- Route configuration in `src/routes/primary.tsx` and `src/routes/secondary.tsx`
|
|
- Using `PageManager.tsx` to manage page navigation, rendering, and state. Normally, you don't need to modify this file.
|
|
- Primary pages (left column) use key-based navigation
|
|
- Secondary pages (right column) use path-based navigation with stack support
|
|
- More details in "Adding a New Page" section below
|
|
|
|
### Internationalization (i18n)
|
|
|
|
- Translation files located in `src/i18n/locales/`
|
|
- Using `react-i18next` for internationalization
|
|
- Supported languages: ar, de, en, es, fa, fr, hi, hu, it, ja, ko, pl, pt-BR, pt-PT, ru, th, zh
|
|
|
|
#### Adding New Language
|
|
|
|
1. Create a new file in `src/i18n/locales/` with the language code (e.g., `th.ts` for Thai)
|
|
2. According to `src/i18n/locales/en.ts`, add translation key-value pairs
|
|
3. Update `src/i18n/index.ts` to include the new language resource
|
|
4. Update `detectLanguage` function in `src/lib/utils.ts` to support detecting the new language
|
|
|
|
## Nostr Protocol Integration
|
|
|
|
### Core Concepts
|
|
|
|
- **Events**: Nostr events (notes, profile updates, etc.). All data in Nostr is represented as events. They have different kinds (kinds) to represent different types of data.
|
|
- **Relays**: Relay servers, which are WebSocket servers that store and forward Nostr events.
|
|
- **NIPs**: Nostr Implementation Proposals
|
|
|
|
### Supported Event Kinds
|
|
|
|
I mean kinds that are supported to be displayed in the feed.
|
|
|
|
- Kind 1: Short Text Note
|
|
- Kind 6: Repost
|
|
- Kind 20: Picture Note
|
|
- Kind 21: Video Note
|
|
- Kind 22: Short Video Note
|
|
- Kind 1068: Poll
|
|
- Kind 1111: Comment
|
|
- Kind 1222: Voice Note
|
|
- Kind 1244: Voice Comment
|
|
- Kind 9802: Highlight
|
|
- Kind 30023: Long-Form Article
|
|
- Kind 31987: Relay Review
|
|
- Kind 34550: Community Definition
|
|
- Kind 30311: Live Event
|
|
- Kind 39000: Group Metadata
|
|
|
|
More details you can find in `src/components/Note/`. If you want to add support for new kinds, you need to create new components under `src/components/Note/` and update `src/components/Note/index.tsx`.
|
|
|
|
Please avoid modifying the framework, such as avatars, usernames, timestamps, and action buttons in the `Note` component. Only add content rendering logic for new types.
|
|
|
|
## Common Modification Scenarios
|
|
|
|
### Adding a New Component
|
|
|
|
1. Create a component folder in `src/components/`
|
|
2. Create `index.tsx` and necessary sub-components
|
|
3. Write styles using Tailwind CSS
|
|
4. If needed, add base UI components in `src/components/ui/`
|
|
|
|
### Adding a New Page
|
|
|
|
#### Adding a Primary Page (Left Column)
|
|
|
|
Primary pages are the main navigation pages that appear in the left column (or full screen on mobile).
|
|
|
|
1. **Create the page component**:
|
|
|
|
```bash
|
|
# Create a new folder under src/pages/primary/
|
|
mkdir src/pages/primary/YourNewPage
|
|
```
|
|
|
|
2. **Implement the component** (`src/pages/primary/YourNewPage/index.tsx`):
|
|
|
|
```tsx
|
|
import PrimaryPageLayout from '@/layouts/PrimaryPageLayout'
|
|
import { TPageRef } from '@/types'
|
|
import { forwardRef } from 'react'
|
|
|
|
const YourNewPage = forwardRef<TPageRef>((_, ref) => {
|
|
return (
|
|
<PrimaryPageLayout ref={ref} title="Your Page Title" icon={<YourIcon />}>
|
|
{/* Your page content */}
|
|
</PrimaryPageLayout>
|
|
)
|
|
})
|
|
|
|
export default YourNewPage
|
|
```
|
|
|
|
**Important**:
|
|
|
|
- Primary pages MUST use `forwardRef<TPageRef>`
|
|
- Wrap content with `PrimaryPageLayout`
|
|
- The ref is used by PageManager for navigation control
|
|
|
|
3. **Register the route** in `src/routes/primary.tsx`:
|
|
|
|
```tsx
|
|
import YourNewPage from '@/pages/primary/YourNewPage'
|
|
|
|
const PRIMARY_ROUTE_CONFIGS: RouteConfig[] = [
|
|
// ... existing routes
|
|
{ key: 'yourNewPage', component: YourNewPage }
|
|
]
|
|
```
|
|
|
|
4. **Navigate to the page** using the `usePrimaryPage` hook:
|
|
|
|
```tsx
|
|
import { usePrimaryPage } from '@/PageManager'
|
|
|
|
const { navigate } = usePrimaryPage()
|
|
navigate('yourNewPage')
|
|
```
|
|
|
|
#### Adding a Secondary Page (Right Column)
|
|
|
|
Secondary pages appear in the right column (or full screen on mobile) and support stack-based navigation.
|
|
|
|
1. **Create the page component**:
|
|
|
|
```bash
|
|
# Create a new folder under src/pages/secondary/
|
|
mkdir src/pages/secondary/YourNewPage
|
|
```
|
|
|
|
2. **Implement the component** (`src/pages/secondary/YourNewPage/index.tsx`):
|
|
|
|
```tsx
|
|
import SecondaryPageLayout from '@/layouts/SecondaryPageLayout'
|
|
import { forwardRef } from 'react'
|
|
|
|
const YourNewPage = forwardRef(({ index }: { index?: number }, ref) => {
|
|
return (
|
|
<SecondaryPageLayout ref={ref} index={index} title="Your Page Title">
|
|
{/* Your page content */}
|
|
</SecondaryPageLayout>
|
|
)
|
|
})
|
|
|
|
export default YourNewPage
|
|
```
|
|
|
|
**Important**:
|
|
|
|
- Secondary pages receive an `index` prop for stack navigation
|
|
- Use `SecondaryPageLayout` for consistent styling
|
|
- The ref enables navigation control
|
|
|
|
3. **Register the route** in `src/routes/secondary.tsx`:
|
|
|
|
```tsx
|
|
import YourNewPage from '@/pages/secondary/YourNewPage'
|
|
|
|
const SECONDARY_ROUTE_CONFIGS = [
|
|
// ... existing routes
|
|
{ path: '/your-path/:id', element: <YourNewPage /> }
|
|
]
|
|
```
|
|
|
|
Add the corresponding path generation function in `src/lib/link.ts` for the new route:
|
|
|
|
```tsx
|
|
export const toYourNewPage = (id: string) => `/your-path/${id}`
|
|
```
|
|
|
|
4. **Navigate to the page**:
|
|
|
|
```tsx
|
|
import { useSecondaryPage } from '@/PageManager'
|
|
import { toYourNewPage } from '@/lib/link'
|
|
|
|
const { push, pop } = useSecondaryPage()
|
|
|
|
// Navigate to new page
|
|
push(toYourNewPage('some-id'))
|
|
|
|
// Navigate back
|
|
pop()
|
|
```
|
|
|
|
5. **Access route parameters**:
|
|
|
|
```tsx
|
|
const YourNewPage = forwardRef(({ id, index }: { id?: string; index?: number }, ref) => {
|
|
console.log('Route param id:', id)
|
|
// ...
|
|
})
|
|
```
|
|
|
|
#### Key Differences
|
|
|
|
| Aspect | Primary Pages | Secondary Pages |
|
|
| -------------- | ----------------------------------- | ------------------------------- |
|
|
| **Location** | Left column (main navigation) | Right column (detail view) |
|
|
| **Navigation** | Replace-based (`navigate`) | Stack-based (`push`/`pop`) |
|
|
| **Layout** | `PrimaryPageLayout` | `SecondaryPageLayout` |
|
|
| **Routes** | Key-based (e.g., 'home', 'explore') | Path-based (e.g., '/notes/:id') |
|
|
|
|
On mobile devices or single-column layouts, primary pages occupy the full screen, while secondary pages are accessed via stack navigation. When navigating to another primary page, it will clear the secondary page stack.
|
|
|
|
### Adding New State Management
|
|
|
|
1. For global state, create a new Provider in `src/providers/`
|
|
2. Add Provider in `App.tsx` in the correct dependency order
|
|
|
|
Or create a singleton service in `src/services/` and use Jotai atoms for state management.
|
|
|
|
### Adding New Business Logic
|
|
|
|
1. Create a new service file in `src/services/`
|
|
2. Export singleton instance
|
|
3. Import and use in anywhere needed
|
|
|
|
### Style Modifications
|
|
|
|
- Global styles: `src/index.css`
|
|
- Tailwind configuration: `tailwind.config.js`
|
|
- Component styles: Use Tailwind class names directly
|