mirror of
https://github.com/zadam/trilium.git
synced 2026-06-25 19:50:53 +02:00
275 lines
16 KiB
Markdown
275 lines
16 KiB
Markdown
# CLAUDE.md
|
|
|
|
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
|
|
|
## Overview
|
|
|
|
Trilium Notes is a hierarchical note-taking application with synchronization, scripting, and rich text editing. TypeScript monorepo using pnpm with multiple apps and shared packages.
|
|
|
|
## Development Commands
|
|
|
|
```bash
|
|
# Setup
|
|
corepack enable && pnpm install
|
|
|
|
# Run
|
|
pnpm server:start # Dev server at http://localhost:8080
|
|
pnpm desktop:start # Electron dev app
|
|
pnpm standalone:start # Standalone client dev
|
|
|
|
# Build
|
|
pnpm client:build # Frontend
|
|
pnpm server:build # Backend
|
|
pnpm desktop:build # Electron
|
|
|
|
# Test
|
|
pnpm test:all # All tests (parallel + sequential)
|
|
pnpm test:parallel # Client + most package tests
|
|
pnpm test:sequential # Server, ckeditor5-mermaid, ckeditor5-math (shared DB)
|
|
pnpm --filter server test # Single package tests
|
|
pnpm coverage # Coverage reports
|
|
|
|
# Lint & Format
|
|
pnpm dev:linter-check # ESLint check
|
|
pnpm dev:linter-fix # ESLint fix
|
|
pnpm dev:format-check # Format check (stricter stylistic rules)
|
|
pnpm dev:format-fix # Format fix
|
|
pnpm typecheck # TypeScript type check across all projects
|
|
```
|
|
|
|
**Running a single test file**: `pnpm --filter server test spec/etapi/search.spec.ts`
|
|
|
|
## Main Applications
|
|
|
|
The four main apps share `packages/trilium-core/` for business logic but differ in runtime:
|
|
|
|
- **client** (`apps/client/`): Preact frontend with jQuery widget system. Shared UI layer used by both server and desktop.
|
|
- **server** (`apps/server/`): Node.js backend (Express, better-sqlite3). Serves the client and provides REST/WebSocket APIs.
|
|
- **desktop** (`apps/desktop/`): Electron wrapper around server + client, running both in a single process.
|
|
- **standalone** (`apps/client-standalone/` + `apps/standalone-desktop/`): Runs the entire stack in the browser — server logic compiled to WASM via sql.js, executed in a service worker. No Node.js dependency at runtime.
|
|
|
|
## Monorepo Structure
|
|
|
|
```
|
|
apps/
|
|
client/ # Preact frontend (shared by server, desktop, standalone)
|
|
server/ # Node.js backend (Express, better-sqlite3)
|
|
desktop/ # Electron (bundles server + client)
|
|
client-standalone/ # Standalone client (WASM + service workers, no Node.js)
|
|
standalone-desktop/ # Standalone desktop variant
|
|
server-e2e/ # Playwright E2E tests for server
|
|
web-clipper/ # Browser extension
|
|
website/ # Project website
|
|
db-compare/, dump-db/, edit-docs/, build-docs/, icon-pack-builder/
|
|
|
|
packages/
|
|
trilium-core/ # Core business logic: entities, services, SQL, sync
|
|
commons/ # Shared interfaces and utilities
|
|
ckeditor5/ # Custom rich text editor bundle
|
|
codemirror/ # Code editor integration
|
|
highlightjs/ # Syntax highlighting
|
|
share-theme/ # Theme for shared/published notes
|
|
ckeditor5-admonition/, ckeditor5-footnotes/, ckeditor5-math/, ckeditor5-mermaid/
|
|
ckeditor5-keyboard-marker/, express-partial-content/, pdfjs-viewer/, splitjs/
|
|
turndown-plugin-gfm/
|
|
```
|
|
|
|
Use `pnpm --filter <package-name> <command>` to run commands in specific packages.
|
|
|
|
## Core Architecture
|
|
|
|
### Three-Layer Cache System
|
|
|
|
All data access goes through cache layers — never bypass with direct DB queries:
|
|
|
|
- **Becca** (`packages/trilium-core/src/becca/`): Server-side entity cache. Access via `becca.notes[noteId]`.
|
|
- **Froca** (`apps/client/src/services/froca.ts`): Client-side mirror synced via WebSocket. Access via `froca.getNote()`.
|
|
- **Shaca** (`apps/server/src/share/`): Optimized cache for shared/published notes.
|
|
|
|
**Critical**: Always use cache methods, not direct DB writes. Cache methods create `EntityChange` records needed for synchronization.
|
|
|
|
### Entity System
|
|
|
|
Core entities live in `packages/trilium-core/src/becca/entities/` (not `apps/server/`):
|
|
|
|
- `BNote` — Notes with content and metadata
|
|
- `BBranch` — Multi-parent tree relationships (cloning supported)
|
|
- `BAttribute` — Key-value metadata (labels and relations)
|
|
- `BRevision` — Version history
|
|
- `BOption` — Application configuration
|
|
- `BBlob` — Binary content storage
|
|
|
|
Entities extend `AbstractBeccaEntity<T>` with built-in change tracking, hash generation, and date management.
|
|
|
|
### Entity Change & Sync Protocol
|
|
|
|
Every entity modification creates an `EntityChange` record driving sync:
|
|
1. Login with HMAC authentication (document secret + timestamp)
|
|
2. Push changes → Pull changes → Push again (conflict resolution)
|
|
3. Content hash verification with retry loop
|
|
|
|
Sync services: `packages/trilium-core/src/services/sync.ts`, `syncMutexService`, `syncUpdateService`.
|
|
|
|
### Widget-Based UI
|
|
|
|
Frontend widgets in `apps/client/src/widgets/`:
|
|
- `BasicWidget` / `TypedBasicWidget` — Base classes (jQuery `this.$widget` for DOM)
|
|
- `NoteContextAwareWidget` — Responds to note changes
|
|
- `RightPanelWidget` — Sidebar widgets with position ordering
|
|
- Type-specific widgets in `type_widgets/` directory
|
|
|
|
**Widget lifecycle**: `doRenderBody()` for initial render, `refreshWithNote()` for note changes, `entitiesReloadedEvent({loadResults})` for entity updates. Uses jQuery — don't mix React patterns.
|
|
|
|
Fluent builder pattern: `.child()`, `.class()`, `.css()` chaining with position-based ordering.
|
|
|
|
### API Architecture
|
|
|
|
- **Internal API** (`apps/server/src/routes/api/`): REST endpoints, trusts frontend
|
|
- **ETAPI** (`apps/server/src/etapi/`): External API with basic auth tokens — maintain backwards compatibility
|
|
- **WebSocket** (`apps/server/src/services/ws.ts`): Real-time sync
|
|
|
|
### Platform Abstraction
|
|
|
|
`packages/trilium-core/src/services/platform.ts` defines `PlatformProvider` interface with implementations in `apps/desktop/`, `apps/server/`, and `apps/client-standalone/`. Singleton via `initPlatform()`/`getPlatform()`.
|
|
|
|
**PlatformProvider** provides:
|
|
- `crash(message)` — Platform-specific fatal error handling
|
|
- `getEnv(key)` — Environment variable access (server/desktop use `process.env`, standalone maps URL query params like `?safeMode` → `TRILIUM_SAFE_MODE`)
|
|
- `isElectron`, `isMac`, `isWindows` — Platform detection flags
|
|
|
|
**Critical rules for `trilium-core`**:
|
|
- **No `process.env` in core** — use `getPlatform().getEnv()` instead (not available in standalone/browser)
|
|
- **No `import path from "path"` in core** — Node's `path` module is externalized in browser builds. Use `packages/trilium-core/src/services/utils/path.ts` for `extname()`/`basename()` equivalents
|
|
- **No Node.js built-in modules in core** — core runs in both Node.js and the browser (standalone). Use platform-agnostic alternatives or platform providers
|
|
- **Platform detection via functions** — `isElectron()`, `isMac()`, `isWindows()` from `utils/index.ts` are functions (not constants) that call `getPlatform()`. They can only be called after `initializeCore()`, not at module top-level. If used in static definitions, wrap in a closure: `value: () => isWindows() ? "0.9" : "1.0"`
|
|
- **Barrel import caution** — `import { x } from "@triliumnext/core"` loads ALL core exports. Early-loading modules like `config.ts` should import specific subpaths (e.g. `@triliumnext/core/src/services/utils/index`) to avoid circular dependencies or initialization ordering issues
|
|
- **Electron IPC** — In desktop mode, client API calls use Electron IPC (not HTTP). The IPC handler in `apps/server/src/routes/electron.ts` must be registered via `utils.isElectron` from the **server's** utils (which correctly checks `process.versions["electron"]`), not from core's utils
|
|
|
|
### Database
|
|
|
|
SQLite via `better-sqlite3`. SQL abstraction in `packages/trilium-core/src/services/sql/` with `DatabaseProvider` interface, prepared statement caching, and transaction support.
|
|
|
|
- Schema: `apps/server/src/assets/db/schema.sql`
|
|
- Migrations: `apps/server/src/migrations/YYMMDD_HHMM__description.sql`
|
|
|
|
### Internationalization
|
|
- Translation files in `apps/client/src/translations/`
|
|
- Supported languages: English, German, Spanish, French, Romanian, Chinese
|
|
- **Only add new translation keys to `en/translation.json`** — translations for other languages are managed via Weblate and will be contributed by the community
|
|
- Third-party components (e.g., mind-map context menu) should use i18next `t()` for their labels, with the English strings added to `en/translation.json` under a dedicated namespace (e.g., `"mind-map"`)
|
|
- When a translated string contains **interpolated components** (e.g. links, note references) whose order may vary across languages, use `<Trans>` from `react-i18next` instead of `t()`. This lets translators reorder components freely (e.g. `"<Note/> in <Parent/>"` vs `"in <Parent/>, <Note/>"`)
|
|
- When adding a new locale, follow the step-by-step guide in `docs/Developer Guide/Developer Guide/Concepts/Internationalisation Translations/Adding a new locale.md`
|
|
- **Server-side translations** (e.g. hidden subtree titles) go in `apps/server/src/assets/translations/en/server.json`, not in the client `translation.json`
|
|
|
|
### Electron Desktop App
|
|
- Desktop entry point: `apps/desktop/src/main.ts`, window management: `apps/server/src/services/window.ts`
|
|
- IPC communication: use `electron.ipcMain.on(channel, handler)` on server side, `electron.ipcRenderer.send(channel, data)` on client side
|
|
- Electron-only features should check `isElectron()` from `apps/client/src/services/utils.ts` (client) or `utils.isElectron` (server)
|
|
|
|
Three inheritance mechanisms:
|
|
1. **Standard**: `note.getInheritableAttributes()` walks parent tree
|
|
2. **Child prefix**: `child:label` on parent copies to children
|
|
3. **Template relation**: `#template=noteNoteId` includes template's inheritable attributes
|
|
|
|
### Attribute Inheritance
|
|
|
|
Use `note.getOwnedAttribute()` for direct, `note.getAttribute()` for inherited.
|
|
### Client-Side API Restrictions
|
|
- **Do not use `crypto.randomUUID()`** or other Web Crypto APIs that require secure contexts - Trilium can run over HTTP, not just HTTPS
|
|
- Use `randomString()` from `apps/client/src/services/utils.ts` for generating IDs instead
|
|
|
|
### Shared Types Policy
|
|
- Types shared between client and server belong in `@triliumnext/commons` (`packages/commons/src/lib/`)
|
|
- Import shared types directly from `@triliumnext/commons` - do not re-export them from app-specific modules
|
|
- Keep app-specific types (e.g., `LlmProvider` for server, `StreamCallbacks` for client) in their respective apps
|
|
|
|
## Important Patterns
|
|
|
|
- **Protected notes**: Check `note.isContentAvailable()` before accessing content; use `note.getTitleOrProtected()` for safe title access
|
|
- **Long operations**: Use `TaskContext` for progress reporting via WebSocket
|
|
- **Event system** (`packages/trilium-core/src/services/events.ts`): Events emitted in order (notes → branches → attributes) during load for referential integrity
|
|
- **Search**: Expression-based, scoring happens in-memory — cannot add SQL-level LIMIT/OFFSET without losing scoring
|
|
- **Widget cleanup**: Unsubscribe from events in `cleanup()`/`doDestroy()` to prevent memory leaks
|
|
|
|
## Code Style
|
|
|
|
- 4-space indentation, semicolons always required
|
|
- Double quotes (enforced by format config)
|
|
- Max line length: 100 characters
|
|
- Unix line endings
|
|
- Import sorting via `eslint-plugin-simple-import-sort`
|
|
|
|
## Testing
|
|
|
|
- **Server tests** (`apps/server/spec/`): Vitest, must run sequentially (shared DB), forks pool, max 6 workers
|
|
- **Client tests** (`apps/client/src/`): Vitest with happy-dom environment, can run in parallel
|
|
- **E2E tests** (`apps/server-e2e/`): Playwright, Chromium, server started automatically on port 8082
|
|
- **ETAPI tests** (`apps/server/spec/etapi/`): External API contract tests
|
|
|
|
## Documentation
|
|
|
|
- `docs/Script API/` — Auto-generated, never edit directly
|
|
- `docs/User Guide/` — Edit via `pnpm edit-docs:edit-docs`, not manually
|
|
- `docs/Developer Guide/` and `docs/Release Notes/` — Safe for direct Markdown editing
|
|
|
|
## Key Entry Points
|
|
|
|
- `apps/server/src/main.ts` — Server startup
|
|
- `apps/client/src/desktop.ts` — Client initialization
|
|
- `packages/trilium-core/src/becca/becca.ts` — Backend data management
|
|
- `apps/client/src/services/froca.ts` — Frontend cache
|
|
- `apps/server/src/routes/routes.ts` — API route registration
|
|
- `packages/trilium-core/src/services/sql/sql.ts` — Database abstraction
|
|
|
|
### Adding Hidden System Notes
|
|
The hidden subtree (`_hidden`) contains system notes with predictable IDs (prefixed with `_`). Defined in `apps/server/src/services/hidden_subtree.ts` via the `HiddenSubtreeItem` interface from `@triliumnext/commons`.
|
|
|
|
1. Add the note definition to `buildHiddenSubtreeDefinition()` in `apps/server/src/services/hidden_subtree.ts`
|
|
2. Add a translation key for the title in `apps/server/src/assets/translations/en/server.json` under `"hidden-subtree"`
|
|
3. The note is auto-created on startup by `checkHiddenSubtree()` — uses deterministic IDs so all sync cluster instances generate the same structure
|
|
4. Key properties: `id` (must start with `_`), `title`, `type`, `icon` (format: `bx-icon-name` without `bx ` prefix), `attributes`, `children`, `content`
|
|
5. Use `enforceAttributes: true` to keep attributes in sync, `enforceBranches: true` for correct placement, `enforceDeleted: true` to remove deprecated notes
|
|
6. For launcher bar entries, see `hidden_subtree_launcherbar.ts`; for templates, see `hidden_subtree_templates.ts`
|
|
|
|
### Writing to Notes from Server Services
|
|
- `note.setContent()` requires a CLS (Continuation Local Storage) context — wrap calls in `cls.init(() => { ... })` (from `apps/server/src/services/cls.ts`)
|
|
- Operations called from Express routes already have CLS context; standalone services (schedulers, Electron IPC handlers) do not
|
|
|
|
### Adding New LLM Tools
|
|
Tools are defined using `defineTools()` in `apps/server/src/services/llm/tools/` and automatically registered for both the LLM chat and MCP server.
|
|
|
|
1. Add the tool definition in the appropriate module (`note_tools.ts`, `attribute_tools.ts`, `attachment_tools.ts`, `hierarchy_tools.ts`) or create a new module
|
|
2. Each tool needs: `description`, `inputSchema` (Zod), `execute` function, and optionally `mutates: true` for write operations
|
|
3. If creating a new module, wrap tools in `defineTools({...})` and add the registry to `allToolRegistries` in `tools/index.ts`
|
|
4. Add a client-side friendly name in `apps/client/src/translations/en/translation.json` under `llm.tools.<tool_name>` — use **imperative tense** (e.g. "Search notes", "Create note", "Get attributes"), not present continuous
|
|
5. Use ETAPI (`apps/server/src/etapi/`) as inspiration for what fields to expose, but **do not import ETAPI mappers** — inline the field mappings directly in the tool so the LLM layer stays decoupled from the API layer
|
|
|
|
### Updating PDF.js
|
|
1. Update `pdfjs-dist` version in `packages/pdfjs-viewer/package.json`
|
|
2. Run `npx tsx scripts/update-viewer.ts` from that directory
|
|
3. Run `pnpm build` to verify success
|
|
4. Commit all changes including updated viewer files
|
|
|
|
### Database Migrations
|
|
- Add migration scripts in `apps/server/src/migrations/`
|
|
- Update schema in `apps/server/src/assets/db/schema.sql`
|
|
|
|
### Server-Side Static Assets
|
|
- Static assets (templates, SQL, translations, etc.) go in `apps/server/src/assets/`
|
|
- Access them at runtime via `RESOURCE_DIR` from `apps/server/src/services/resource_dir.ts` (e.g. `path.join(RESOURCE_DIR, "llm", "skills", "file.md")`)
|
|
- **Do not use `import.meta.url`/`fileURLToPath`** to resolve file paths — the server is bundled into CJS for production, so `import.meta.url` will not point to the source directory
|
|
- **Do not use `__dirname` with relative paths** from source files — after bundling, `__dirname` points to the bundle output, not the original source tree
|
|
|
|
## MCP Server
|
|
- Trilium exposes an MCP (Model Context Protocol) server at `http://localhost:8080/mcp`, configured in `.mcp.json`
|
|
- The MCP server is **only available when the Trilium server is running** (`pnpm run server:start`)
|
|
- It provides tools for reading, searching, and modifying notes directly from the AI assistant
|
|
- Use it to interact with actual note data when developing or debugging note-related features
|
|
|
|
## Build System Notes
|
|
- Uses pnpm for monorepo management
|
|
- Vite for fast development builds
|
|
- ESBuild for production optimization
|
|
- pnpm workspaces for dependency management
|
|
- Docker support with multi-stage builds
|