mirror of
https://github.com/zadam/trilium.git
synced 2025-12-03 22:14:24 +01:00
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
335 lines
15 KiB
Markdown
335 lines
15 KiB
Markdown
# Trilium Notes - AI Coding Agent Instructions
|
|
|
|
## Project Overview
|
|
|
|
Trilium Notes is a hierarchical note-taking application with advanced features like synchronization, scripting, and rich text editing. Built as a TypeScript monorepo using pnpm, it implements a three-layer caching architecture (Becca/Froca/Shaca) with a widget-based UI system and supports extensive user scripting capabilities.
|
|
|
|
## Essential Architecture Patterns
|
|
|
|
### Three-Layer Cache System (Critical to Understand)
|
|
- **Becca** (`apps/server/src/becca/`): Server-side entity cache, primary data source
|
|
- **Froca** (`apps/client/src/services/froca.ts`): Client-side mirror synchronized via WebSocket
|
|
- **Shaca** (`apps/server/src/share/`): Optimized cache for public/shared notes
|
|
|
|
**Key insight**: Never bypass these caches with direct DB queries. Always use `becca.notes[noteId]`, `froca.getNote()`, or equivalent cache methods.
|
|
|
|
### Entity Relationship Model
|
|
Notes use a **multi-parent tree** via branches:
|
|
- `BNote` - The note content and metadata
|
|
- `BBranch` - Tree relationships (one note can have multiple parents via cloning)
|
|
- `BAttribute` - Key-value metadata attached to notes (labels and relations)
|
|
|
|
### Entity Change System & Sync
|
|
Every entity modification (notes, branches, attributes) creates an `EntityChange` record that drives synchronization:
|
|
|
|
```typescript
|
|
// Entity changes are automatically tracked
|
|
note.title = "New Title";
|
|
note.save(); // Creates EntityChange record with changeId
|
|
|
|
// Sync protocol via WebSocket
|
|
ws.sendMessage({ type: 'sync-pull-in-progress', ... });
|
|
```
|
|
|
|
**Critical**: This is why you must use Becca/Froca methods instead of direct DB writes - they create the change tracking records needed for sync.
|
|
|
|
### Entity Lifecycle & Events
|
|
The event system (`apps/server/src/services/events.ts`) broadcasts entity lifecycle events:
|
|
|
|
```typescript
|
|
// Subscribe to events in widgets or services
|
|
eventService.subscribe('noteChanged', ({ noteId }) => {
|
|
// React to note changes
|
|
});
|
|
|
|
// Common events: noteChanged, branchChanged, attributeChanged, noteDeleted
|
|
// Widget method: entitiesReloadedEvent({loadResults}) for handling reloads
|
|
```
|
|
|
|
**Becca loader priorities**: Events are emitted in order (notes → branches → attributes) during initial load to ensure referential integrity.
|
|
|
|
### TaskContext for Long Operations
|
|
Use `TaskContext` for operations with progress reporting (imports, exports, bulk operations):
|
|
|
|
```typescript
|
|
const taskContext = new TaskContext("task-id", "import", "Import Notes");
|
|
taskContext.increaseProgressCount();
|
|
|
|
// WebSocket messages: { type: 'taskProgressCount', taskId, taskType, data, progressCount }
|
|
|
|
**Pattern**: All long-running operations (delete note trees, export, import) use TaskContext to send WebSocket updates to the frontend.
|
|
|
|
### Protected Session Handling
|
|
Protected notes require an active encryption session:
|
|
|
|
```typescript
|
|
// Always check before accessing protected content
|
|
if (note.isContentAvailable()) {
|
|
const content = note.getContent(); // Safe
|
|
} else {
|
|
const title = note.getTitleOrProtected(); // Returns "[protected]"
|
|
}
|
|
|
|
// Protected session management
|
|
protectedSessionService.isProtectedSessionAvailable() // Check session
|
|
protectedSessionService.startProtectedSession() // After password entry
|
|
```
|
|
|
|
**Session timeout**: Protected sessions expire after inactivity. The encryption key is kept in memory only.
|
|
|
|
### Attribute Inheritance Patterns
|
|
Attributes can be inherited through three mechanisms:
|
|
|
|
```typescript
|
|
// 1. Standard inheritance (#hidePromotedAttributes ~hidePromotedAttributes)
|
|
note.getInheritableAttributes() // Walks up parent tree
|
|
|
|
// 2. Child prefix inheritance (child:label copies to children)
|
|
parentNote.setLabel("child:icon", "book") // All children inherit this
|
|
|
|
// 3. Template relation inheritance (#template=templateNoteId)
|
|
note.setRelation("template", templateNoteId)
|
|
note.getInheritedAttributes() // Includes template's inheritable attributes
|
|
```
|
|
|
|
**Cycle prevention**: Inheritance tracking prevents infinite loops when notes reference each other.
|
|
|
|
### Widget-Based UI Architecture
|
|
All UI components extend from widget base classes (`apps/client/src/widgets/`):
|
|
|
|
```typescript
|
|
// Right panel widget (sidebar)
|
|
class MyWidget extends RightPanelWidget {
|
|
get position() { return 100; } // Order in panel
|
|
get parentWidget() { return 'right-pane'; }
|
|
isEnabled() { return this.note && this.note.hasLabel('myLabel'); }
|
|
async refreshWithNote(note) { /* Update UI */ }
|
|
}
|
|
|
|
// Note-aware widget (responds to note changes)
|
|
class MyNoteWidget extends NoteContextAwareWidget {
|
|
async refreshWithNote(note) { /* Refresh when note changes */ }
|
|
async entitiesReloadedEvent({loadResults}) { /* Handle entity updates */ }
|
|
}
|
|
```
|
|
|
|
**Important**: Widgets use jQuery (`this.$widget`) for DOM manipulation. Don't mix React patterns here.
|
|
|
|
## Development Workflow
|
|
|
|
### Running & Testing
|
|
```bash
|
|
# From root directory
|
|
pnpm install # Install dependencies
|
|
corepack enable # Enable pnpm if not available
|
|
pnpm server:start # Dev server (http://localhost:8080)
|
|
pnpm server:start-prod # Production mode server
|
|
pnpm desktop:start # Desktop app development
|
|
pnpm server:test spec/etapi/search.spec.ts # Run specific test
|
|
pnpm test:parallel # Client tests (can run parallel)
|
|
pnpm test:sequential # Server tests (sequential due to shared DB)
|
|
pnpm test:all # All tests (parallel + sequential)
|
|
pnpm coverage # Generate coverage reports
|
|
pnpm typecheck # Type check all projects
|
|
```
|
|
|
|
### Building
|
|
```bash
|
|
pnpm client:build # Build client application
|
|
pnpm server:build # Build server application
|
|
pnpm desktop:build # Build desktop application
|
|
```
|
|
|
|
### Test Organization
|
|
- **Server tests** (`apps/server/spec/`): Must run sequentially (shared database state)
|
|
- **Client tests** (`apps/client/src/`): Can run in parallel
|
|
- **E2E tests** (`apps/server-e2e/`): Use Playwright for integration testing
|
|
- **ETAPI tests** (`apps/server/spec/etapi/`): External API contract tests
|
|
|
|
**Pattern**: When adding new API endpoints, add tests in `spec/etapi/` following existing patterns (see `search.spec.ts`).
|
|
|
|
### Monorepo Navigation
|
|
```
|
|
apps/
|
|
client/ # Frontend (shared by server & desktop)
|
|
server/ # Node.js backend with REST API
|
|
desktop/ # Electron wrapper
|
|
web-clipper/ # Browser extension for saving web content
|
|
db-compare/ # Database comparison tool
|
|
dump-db/ # Database export utility
|
|
edit-docs/ # Documentation editing tools
|
|
packages/
|
|
commons/ # Shared types and utilities
|
|
ckeditor5/ # Custom rich text editor with Trilium-specific plugins
|
|
codemirror/ # Code editor integration
|
|
highlightjs/ # Syntax highlighting
|
|
share-theme/ # Theme for shared/published notes
|
|
ckeditor5-admonition/ # Admonition blocks plugin
|
|
ckeditor5-footnotes/ # Footnotes plugin
|
|
ckeditor5-math/ # Math equations plugin
|
|
ckeditor5-mermaid/ # Mermaid diagrams plugin
|
|
```
|
|
|
|
**Filter commands**: Use `pnpm --filter server test` to run commands in specific packages.
|
|
|
|
## Critical Code Patterns
|
|
|
|
### ETAPI Backwards Compatibility
|
|
When adding query parameters to ETAPI endpoints (`apps/server/src/etapi/`), maintain backwards compatibility by checking if new params exist before changing response format.
|
|
|
|
**Pattern**: ETAPI consumers expect specific response shapes. Always check for breaking changes.
|
|
|
|
### Frontend-Backend Communication
|
|
- **REST API**: `apps/server/src/routes/api/` - Internal endpoints (no auth required when `noAuthentication=true`)
|
|
- **ETAPI**: `apps/server/src/etapi/` - External API with authentication
|
|
- **WebSocket**: Real-time sync via `apps/server/src/services/ws.ts`
|
|
|
|
**Auth note**: ETAPI uses basic auth with tokens. Internal API endpoints trust the frontend.
|
|
|
|
### Database Migrations
|
|
- Add scripts in `apps/server/src/migrations/YYMMDD_HHMM__description.sql`
|
|
- Update schema in `apps/server/src/assets/db/schema.sql`
|
|
- Never bypass Becca cache after migrations
|
|
|
|
## Common Pitfalls
|
|
|
|
1. **Never bypass the cache layers** - Always use `becca.notes[noteId]`, `froca.getNote()`, or equivalent cache methods. Direct database queries will cause sync issues between Becca/Froca/Shaca and won't create EntityChange records needed for synchronization.
|
|
|
|
2. **Protected notes require session check** - Before accessing `note.title` or `note.getContent()` on protected notes, check `note.isContentAvailable()` or use `note.getTitleOrProtected()` which handles this automatically.
|
|
|
|
3. **Widget lifecycle matters** - Override `refreshWithNote()` for note changes, `doRenderBody()` for initial render, `entitiesReloadedEvent()` for entity updates. Widgets use jQuery (`this.$widget`) - don't mix React patterns.
|
|
|
|
4. **Tests run differently** - Server tests must run sequentially (shared database state), client tests can run in parallel. Use `pnpm test:sequential` for backend, `pnpm test:parallel` for frontend.
|
|
|
|
5. **ETAPI requires authentication** - ETAPI endpoints use basic auth with tokens. Internal API endpoints (`apps/server/src/routes/api/`) trust the frontend when `noAuthentication=true`.
|
|
|
|
6. **Search expressions are evaluated in memory** - The search service loads all matching notes, scores them in JavaScript, then sorts. You cannot add SQL-level LIMIT/OFFSET without losing scoring functionality.
|
|
|
|
7. **Documentation edits have rules** - `docs/Script API/` is auto-generated (never edit directly). `docs/User Guide/` should be edited via `pnpm edit-docs:edit-docs`, not manually. Only `docs/Developer Guide/` and `docs/Release Notes/` are safe for direct Markdown editing.
|
|
|
|
8. **pnpm workspace filtering** - Use `pnpm --filter server <command>` or shorthand `pnpm server:test` defined in root `package.json`. Note the `--filter` syntax, not `-F` or other shortcuts.
|
|
|
|
9. **Event subscription cleanup** - When subscribing to events in widgets, unsubscribe in `cleanup()` or `doDestroy()` to prevent memory leaks.
|
|
|
|
10. **Attribute inheritance can be complex** - When checking for labels/relations, use `note.getOwnedAttribute()` for direct attributes or `note.getAttribute()` for inherited ones. Don't assume attributes are directly on the note.
|
|
|
|
## TypeScript Configuration
|
|
|
|
- **Project references**: Monorepo uses TypeScript project references (`tsconfig.json`)
|
|
- **Path mapping**: Use relative imports, not path aliases
|
|
- **Build order**: `pnpm typecheck` builds all projects in dependency order
|
|
- **Build system**: Uses Vite for fast development, ESBuild for production optimization
|
|
- **Patches**: Custom patches in `patches/` directory for CKEditor and other dependencies
|
|
|
|
## Key Files for Context
|
|
|
|
- `apps/server/src/becca/entities/bnote.ts` - Note entity methods
|
|
- `apps/client/src/services/froca.ts` - Frontend cache API
|
|
- `apps/server/src/services/search/services/search.ts` - Search implementation
|
|
- `apps/server/src/routes/routes.ts` - API route registration
|
|
- `apps/client/src/widgets/basic_widget.ts` - Widget base class
|
|
- `apps/server/src/main.ts` - Server startup entry point
|
|
- `apps/client/src/desktop.ts` - Client initialization
|
|
- `apps/server/src/services/backend_script_api.ts` - Scripting API
|
|
- `apps/server/src/assets/db/schema.sql` - Database schema
|
|
|
|
## Note Types and Features
|
|
|
|
Trilium supports multiple note types with specialized widgets in `apps/client/src/widgets/type_widgets/`:
|
|
- **Text**: Rich text with CKEditor5 (markdown import/export)
|
|
- **Code**: Syntax-highlighted code editing with CodeMirror
|
|
- **File**: Binary file attachments
|
|
- **Image**: Image display with editing capabilities
|
|
- **Canvas**: Drawing/diagramming with Excalidraw
|
|
- **Mermaid**: Diagram generation
|
|
- **Relation Map**: Visual note relationship mapping
|
|
- **Web View**: Embedded web pages
|
|
- **Doc/Book**: Hierarchical documentation structure
|
|
|
|
### Collections
|
|
Notes can be marked with the `#collection` label to enable collection view modes. Collections support multiple view types:
|
|
- **List**: Standard list view
|
|
- **Grid**: Card/grid layout
|
|
- **Calendar**: Calendar-based view
|
|
- **Table**: Tabular data view
|
|
- **GeoMap**: Geographic map view
|
|
- **Board**: Kanban-style board
|
|
- **Presentation**: Slideshow presentation mode
|
|
|
|
View types are configured via `#viewType` label (e.g., `#viewType=table`). Each view mode stores its configuration in a separate attachment (e.g., `table.json`). Collections are organized separately from regular note type templates in the note creation menu.
|
|
|
|
## Common Development Tasks
|
|
|
|
### Adding New Note Types
|
|
1. Create widget in `apps/client/src/widgets/type_widgets/`
|
|
2. Register in `apps/client/src/services/note_types.ts`
|
|
3. Add backend handling in `apps/server/src/services/notes.ts`
|
|
|
|
### Extending Search
|
|
- Search expressions handled in `apps/server/src/services/search/`
|
|
- Add new search operators in search context files
|
|
- Remember: scoring happens in-memory, not at database level
|
|
|
|
### Custom CKEditor Plugins
|
|
- Create new package in `packages/` following existing plugin structure
|
|
- Register in `packages/ckeditor5/src/plugins.ts`
|
|
- See `ckeditor5-admonition`, `ckeditor5-footnotes`, `ckeditor5-math`, `ckeditor5-mermaid` for examples
|
|
|
|
### Database Migrations
|
|
- Add migration scripts in `apps/server/src/migrations/YYMMDD_HHMM__description.sql`
|
|
- Update schema in `apps/server/src/assets/db/schema.sql`
|
|
- Never bypass Becca cache after migrations
|
|
|
|
## Security & Features
|
|
|
|
### Security Considerations
|
|
- Per-note encryption with granular protected sessions
|
|
- CSRF protection for API endpoints
|
|
- OpenID and TOTP authentication support
|
|
- Sanitization of user-generated content
|
|
|
|
### Scripting System
|
|
Trilium provides powerful user scripting capabilities:
|
|
- **Frontend scripts**: Run in browser context with UI access
|
|
- **Backend scripts**: Run in Node.js context with full API access
|
|
- Script API documentation in `docs/Script API/`
|
|
- Backend API available via `api` object in script context
|
|
|
|
### Internationalization
|
|
- Translation files in `apps/client/src/translations/`
|
|
- Use translation system via `t()` function
|
|
- Automatic pluralization: Add `_other` suffix to translation keys (e.g., `item` and `item_other` for singular/plural)
|
|
|
|
## Testing Conventions
|
|
|
|
```typescript
|
|
// ETAPI test pattern
|
|
describe("etapi/feature", () => {
|
|
beforeAll(async () => {
|
|
config.General.noAuthentication = false;
|
|
app = await buildApp();
|
|
token = await login(app);
|
|
});
|
|
|
|
it("should test feature", async () => {
|
|
const response = await supertest(app)
|
|
.get("/etapi/notes?search=test")
|
|
.auth(USER, token, { type: "basic" })
|
|
.expect(200);
|
|
|
|
expect(response.body.results).toBeDefined();
|
|
});
|
|
});
|
|
```
|
|
|
|
## Questions to Verify Understanding
|
|
|
|
Before implementing significant changes, confirm:
|
|
- Is this touching the cache layer? (Becca/Froca/Shaca must stay in sync via EntityChange records)
|
|
- Does this change API response shape? (Check backwards compatibility for ETAPI)
|
|
- Are you adding search features? (Understand expression-based architecture and in-memory scoring first)
|
|
- Is this a new widget? (Know which base class and lifecycle methods to use)
|
|
- Does this involve protected notes? (Check `isContentAvailable()` before accessing content)
|
|
- Is this a long-running operation? (Use TaskContext for progress reporting)
|
|
- Are you working with attributes? (Understand inheritance patterns: direct, child-prefix, template)
|