12 KiB
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
# 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
- Component Structure: Each major feature component is typically in its own folder, containing index.tsx and related sub-components
- Styling: Use Tailwind CSS utility classes, complex components can use class-variance-authority (cva)
- Type Safety: All components should have explicit TypeScript type definitions
- 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 eventsindexed-db.service.ts- IndexedDB data storagelocal-storage.service.ts- LocalStorage managementmedia-upload.service.ts- Media upload servicetranslation.service.ts- Translation servicelightning.service.ts- Lightning Network integrationrelay-info.service.ts- Relay information managementblossom.service.ts- Blossom integrationcustom-emoji.service.ts- Custom emoji managementlibre-translate.service.ts- LibreTranslate API integrationmedia-manager.service.ts- Managing media play statemodal-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 retrievalpost-editor-cache.service.ts- Caching post editor content to prevent data lossweb.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.tsxandsrc/routes/secondary.tsx - Using
PageManager.tsxto 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-i18nextfor 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
- Create a new file in
src/i18n/locales/with the language code (e.g.,th.tsfor Thai) - According to
src/i18n/locales/en.ts, add translation key-value pairs - Update
src/i18n/index.tsto include the new language resource - Update
detectLanguagefunction insrc/lib/utils.tsto 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
- Create a component folder in
src/components/ - Create
index.tsxand necessary sub-components - Write styles using Tailwind CSS
- 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).
-
Create the page component:
# Create a new folder under src/pages/primary/ mkdir src/pages/primary/YourNewPage -
Implement the component (
src/pages/primary/YourNewPage/index.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 YourNewPageImportant:
- Primary pages MUST use
forwardRef<TPageRef> - Wrap content with
PrimaryPageLayout - The ref is used by PageManager for navigation control
- Primary pages MUST use
-
Register the route in
src/routes/primary.tsx:import YourNewPage from '@/pages/primary/YourNewPage' const PRIMARY_ROUTE_CONFIGS: RouteConfig[] = [ // ... existing routes { key: 'yourNewPage', component: YourNewPage } ] -
Navigate to the page using the
usePrimaryPagehook: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.
-
Create the page component:
# Create a new folder under src/pages/secondary/ mkdir src/pages/secondary/YourNewPage -
Implement the component (
src/pages/secondary/YourNewPage/index.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 YourNewPageImportant:
- Secondary pages receive an
indexprop for stack navigation - Use
SecondaryPageLayoutfor consistent styling - The ref enables navigation control
- Secondary pages receive an
-
Register the route in
src/routes/secondary.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.tsfor the new route:export const toYourNewPage = (id: string) => `/your-path/${id}` -
Navigate to the page:
import { useSecondaryPage } from '@/PageManager' import { toYourNewPage } from '@/lib/link' const { push, pop } = useSecondaryPage() // Navigate to new page push(toYourNewPage('some-id')) // Navigate back pop() -
Access route parameters:
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
- For global state, create a new Provider in
src/providers/ - Add Provider in
App.tsxin the correct dependency order
Or create a singleton service in src/services/ and use Jotai atoms for state management.
Adding New Business Logic
- Create a new service file in
src/services/ - Export singleton instance
- 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