This file contains guidelines for agentic coding agents working in the Grimoire repository.
For detailed project architecture, patterns, and conventions, see CLAUDE.md.
npm run dev # Start development server with HMR
npm run build # TypeScript check + production build
npm run preview # Preview production build locallynpm run lint # Run ESLint (fails on errors)
npm run lint:fix # Auto-fix ESLint issues + formatting
npm run format # Format with Prettier
npm run format:check # Check formatting without changesnpm test # Run tests in watch mode
npm run test:ui # Visual test explorer
npm run test:run # Single test run (CI mode)Single Test: Use vitest run with file pattern:
npm run test:run src/lib/nostr-utils.test.tsAlways run before committing:
npm run lint && npm run test:run && npm run build- Path Alias: Use
@/forsrc/directory (configured in Vite & Vitest) - Import Order: External libs → Applesauce → Local
@/modules → Relative imports - Applesauce: Prefer helpers from
applesauce-core/helpersandapplesauce-common/helpers - No Default Exports: Use named exports for better tree-shaking
// ✅ Correct import order
import { useState } from "react";
import { use$ } from "applesauce-react/hooks";
import { getProfileContent } from "applesauce-core/helpers";
import { cn } from "@/lib/utils";
import { UserName } from "./components/UserName";- Strict Mode: Project uses strict TypeScript configuration
- No
any: ESLint rule disabled, but prefer proper types - Applesauce Types: Use types from
applesauce-coreandnostr-tools - Local Types: Extend in
src/types/when needed
// ✅ Use proper types from Applesauce
import type { NostrEvent, ProfileContent } from "applesauce-core/helpers";
import type { EventPointer } from "nostr-tools/nip19";- React 19: Use latest features (hooks, concurrent rendering)
- No Default Exports: Components use named exports
- Props Interface: Always define props interface with JSDoc comments
- Destructured Props: Destructure props in function signature
// ✅ Component pattern
interface QuotedEventProps {
/** EventPointer with optional relay hints */
eventPointer?: EventPointer;
/** Depth level for nesting */
depth?: number;
}
export function QuotedEvent({ eventPointer, depth = 0 }: QuotedEventProps) {
// Component logic
}- Applesauce Helpers: Cache internally - NO
useMemoneeded - Custom Helpers: Use
useMemofor complex transformations - Stable References: Use
useStableValue/useStableArrayfor filters/options
// ❌ WRONG - Unnecessary memoization
const title = useMemo(() => getArticleTitle(event), [event]);
// ✅ CORRECT - Helpers cache internally
const title = getArticleTitle(event);- TailwindCSS: Primary styling approach
- CSS Variables: Theme tokens in HSL format (see
index.css) - Utility Function: Use
cn()from@/lib/utilsfor class merging - Dark Mode: Default (controlled via HTML class)
// ✅ Styling pattern
import { cn } from "@/lib/utils";
const className = cn(
"flex items-center gap-2 p-2 rounded-lg",
isActive && "bg-primary text-primary-foreground",
className
);- By Domain: Group files by feature/domain (
nostr/,ui/,services/,hooks/) - Colocated Tests: Test files next to source files (
*.test.ts) - Barrel Exports: Use
index.tsfor clean imports - Pure Functions: Business logic in
src/core/logic.ts
- Components: PascalCase with descriptive names (
LiveActivityRenderer) - Hooks: camelCase with
useprefix (useAccount,useProfile) - Utilities: camelCase with descriptive names (
getDisplayName,canAccountSign) - Constants: UPPER_SNAKE_CASE for exports
- Files: kebab-case for utilities, PascalCase for components
- Vitest: Test framework with Node environment
- Polyfills: IndexedDB, WebSocket, localStorage pre-configured
- Setup File:
src/test/setup.tscontains browser API polyfills
- Parsers: All argument parsing logic and edge cases
- Pure Functions: State mutations and business logic
- Utilities: Helper functions and data transformations
- Not UI Components: React components tested manually
describe("parseReqCommand", () => {
describe("kind flag (-k, --kind)", () => {
it("should parse single kind", () => {
const result = parseReqCommand(["-k", "1"]);
expect(result.filter.kinds).toEqual([1]);
});
});
});- Singleton Services: Never create new EventStore, RelayPool, or RelayLiveness instances
- Verification: Always run full verification before committing changes
- Applesauce Caching: Don't use
useMemowith Applesauce helpers - Account Signing: Always check
canSignbefore signing operations - Error Boundaries: Wrap event renderers in error boundaries
- Path Alias: Use
@/for all internal imports
Grimoire is a Nostr protocol explorer with a tiling window manager interface. Each window is a Nostr "app" (profile viewer, event feed, NIP documentation, etc.). Commands are launched Unix-style via Cmd+K palette.
Stack: React 19 + TypeScript + Vite + TailwindCSS + Jotai + Dexie + Applesauce
See CLAUDE.md for detailed architecture, patterns, hooks, and conventions.