{"owner":"Foundry376","repo":"Mailspring","hasSkills":true,"totalSkillsCount":4,"totalTokensCount":3262,"categories":["claude-rule","mcp-config","marketplace","plugin-manifest"],"hasMcp":true,"mcpConfig":{"mcpServers":{"Mailspring":{"command":"npx","args":["-y","@modelcontextprotocol/server-Mailspring"]}}},"found":["CLAUDE.md",".cursor/mcp.json",".claude-plugin/marketplace.json",".claude-plugin/plugin.json"],"skills":{"CLAUDE.md":"# CLAUDE.md\n\nThis file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.\n\n## Build and Development Commands\n\n```bash\n# Install dependencies\nnpm install\n\n# Run the app in development mode (uses --dev flag, data stored in Mailspring-dev folder)\nnpm start\n\n# Run linting (prettier runs as an eslint rule); lint:check reports without fixing\nnpm run lint\nnpm run lint:check\n\n# TypeScript type checking\nnpm run typecheck\n\n# Run all tests, or only spec files whose path matches a regex\nnpm test\nnpm test -- --spec-file-pattern='calendar'\n\n# Run window-specific tests\nnpm run test-window\n\n# Run Playwright end-to-end tests\nnpm run test:e2e\n\n# TypeScript type checking in watch mode\nnpm run tsc-watch\n\n# Build for production\nnpm run build\n```\n\n## Architecture Overview\n\nMailspring is an Electron-based email client written in TypeScript with React. It uses a plugin architecture where features are implemented as internal packages.\n\n### Key Directories\n\n- **`app/src/`** - Core application source code\n  - `browser/` - Main process code (application lifecycle, window management, auto-updates)\n  - `flux/` - Flux-based state management (actions, stores, models, tasks)\n  - `components/` - Reusable React UI components\n  - `services/` - Application services (search, sanitization, etc.)\n  - `registries/` - Extension registries (components, extensions, database objects)\n  - `global/` - Global exports (`mailspring-exports`, `mailspring-component-kit`)\n\n- **`app/internal_packages/`** - Built-in plugins implementing features (composer, message-list, thread-list, preferences, themes, etc.)\n\n> **IMPORTANT:** Application source code lives in **both** `app/src/` and `app/internal_packages/`. When searching for usages of a module, symbol, or pattern, always search both directories. Searching only `app/src/` will miss a large portion of the codebase and lead to incomplete changes.\n\n- **`app/spec/`** - Jasmine test specs\n\n### Core Modules\n\n**Global exports for plugins:**\n- `mailspring-exports` - Core APIs: Actions, Stores, Models, Tasks, Utils, database access\n- `mailspring-component-kit` - Reusable UI components\n\n**Flux Architecture:**\n- **Models** (`flux/models/`) - Data models: Message, Thread, Contact, Account, Folder, Label, etc.\n- **Stores** (`flux/stores/`) - Application state: DatabaseStore, DraftStore, AccountStore, etc.\n- **Tasks** (`flux/tasks/`) - Async operations: SendDraftTask, ChangeFolderTask, etc.\n- **Actions** (`flux/actions.ts`) - Application-wide action dispatcher\n\n### Plugin Structure\n\nEach plugin in `internal_packages/` has:\n- `package.json` - Metadata with `windowTypes` specifying where plugin loads\n- `lib/main.ts` - Entry point with `activate()` and `deactivate()` lifecycle hooks\n- `lib/` - Plugin source code\n- `styles/` - LESS stylesheets\n- `keymaps/` - Keyboard shortcut definitions\n\n## Core Data Flow: Sync Engine, Tasks, and Observable Database\n\n**Important:** The UI is read-only with respect to the database. All database modifications happen in the C++ sync engine (Mailspring-Sync). The Electron app requests changes via Tasks, and the sync engine streams entity changes back to create a real-time UI.\n\n### Sync Engine Communication (`mailsync-process.ts`, `mailsync-bridge.ts`)\n\nThe sync engine is a separate C++ process spawned per account:\n\n1. **Electron → Sync Engine**: JSON messages sent via stdin (task requests, commands)\n2. **Sync Engine → Electron**: Newline-delimited JSON streamed via stdout (database change deltas)\n\n```\n┌─────────────────┐         stdin (JSON)          ┌──────────────────┐\n│   Electron UI   │ ──────────────────────────────▶│  Mailspring-Sync │\n│  (TypeScript)   │                                │      (C++)       │\n│                 │ ◀────────────────────────────── │                  │\n└─────────────────┘    stdout (JSON deltas)        └──────────────────┘\n```\n\nThe `MailsyncBridge` (in main window only) manages sync process lifecycle, listens to `Actions.queueTask`, and forwards tasks to the appropriate account's sync process.\n\n### Task System (`flux/tasks/`)\n\nTasks represent operations the user wants to perform (send email, star thread, move to folder). They are **persisted models** stored in the database.\n\n**Task Lifecycle:**\n1. UI calls `Actions.queueTask(new SomeTask({...}))`\n2. `MailsyncBridge._onQueueTask()` validates and sends to sync engine via stdin\n3. Sync engine executes the task (local changes + remote API calls)\n4. Sync engine persists task status updates and emits deltas\n5. Task completion triggers `onSuccess()` or `onError()` callbacks\n\n**Task States** (`flux/tasks/task.ts`):\n- `local` - Not yet executed\n- `remote` - Local phase complete, waiting for remote\n- `complete` - Finished successfully\n- `cancelled` - Cancelled before completion\n\n**Key Task Classes:**\n- `SendDraftTask`, `DestroyDraftTask` - Email composition\n- `ChangeLabelsTask`, `ChangeFolderTask` - Organization\n- `ChangeStarredTask`, `ChangeUnreadTask` - Status flags\n- `SyncbackMetadataTask` - Plugin metadata sync\n- `SyncbackEventTask` - Calendar event sync\n\n**Undoable Tasks:**\n\nTasks can support undo/redo by implementing `canBeUndone` and `createUndoTask()`. The `UndoRedoStore` automatically registers tasks with `canBeUndone = true` for undo.\n\nTwo patterns exist:\n1. **Toggle pattern** (`ChangeStarredTask`): Undo simply flips a boolean flag\n2. **Snapshot pattern** (`SyncbackMetadataTask`, `SyncbackEventTask`): Store original state in `undoData`, swap on undo\n\n```typescript\n// Snapshot pattern example\nconst undoData = { ics: event.ics, recurrenceStart: event.recurrenceStart };\nevent.ics = newIcs;  // Modify after capturing\nActions.queueTask(SyncbackEventTask.forUpdating({ event, undoData, description: 'Edit event' }));\n```\n\nSee `docs/undo-redo-task-pattern.md` for detailed implementation guide.\n\n### Task Queue (`flux/stores/task-queue.ts`)\n\nThe TaskQueue store observes Task model changes from the database and provides:\n- `queue()` - Active tasks\n- `completed()` - Finished tasks\n- `waitForPerformLocal(task)` - Promise that resolves when task runs locally\n- `waitForPerformRemote(task)` - Promise that resolves when task fully completes\n\n### Observable Database Pattern\n\n**Database is read-only in Electron** (`flux/stores/database-store.ts`):\n- `DatabaseStore.inTransaction()` throws - writes are not allowed\n- Uses SQLite in WAL mode via better-sqlite3 for concurrent reads\n- The sync engine exclusively handles writes\n\n**Change Records** (`flux/stores/database-change-record.ts`):\n\nWhen the sync engine modifies data, it emits JSON deltas that become `DatabaseChangeRecord` objects:\n```typescript\n{\n  type: 'persist' | 'unpersist',\n  objectClass: 'Thread' | 'Message' | ...,\n  objects: Model[],\n  objectsRawJSON: object[]\n}\n```\n\n**Reactive Queries** (`flux/models/query-subscription.ts`):\n\n`QuerySubscription` provides live-updating query results:\n```typescript\n// Subscribe to all unread threads\nconst subscription = new QuerySubscription(\n  DatabaseStore.findAll(Thread).where({ unread: true })\n);\nsubscription.addCallback((threads) => this.setState({ threads }));\n\n// Subscription automatically updates when DatabaseStore triggers\n```\n\n**Observable Integration** (`Rx.Observable.fromQuery`):\n\nWrap queries as RxJS observables for reactive UI updates:\n```typescript\nRx.Observable.fromQuery(DatabaseStore.findAll(Thread))\n  .subscribe(threads => this.updateUI(threads));\n```\n\n**ObservableListDataSource** (`flux/stores/observable-list-data-source.ts`):\n\nAdapts QuerySubscription for virtualized list components (MultiselectList), supporting:\n- Windowed/paginated data loading\n- Selection state management\n- Automatic updates from database changes\n\n### Data Flow Summary\n\n```\nUser Action → Actions.queueTask() → MailsyncBridge → stdin → Sync Engine\n                                                              │\n                                                              ▼\nUI Updates ← QuerySubscription ← DatabaseStore.trigger() ← stdout deltas\n```\n\n## Comment Style\n\nWrite for a technical reader who prefers self-documenting code. Prefer clearer names and\nsmaller functions over commentary; a comment that restates the code should be deleted.\n\n- State **current behavior and rationale** — why the code is the way it is, and what a\n  reader would otherwise get wrong. Never narrate thought process, stream of\n  consciousness, or the path you took to the answer.\n- Never describe history: where code used to live, what it was folded out of, what the\n  previous implementation did, or what a diff changed. Git records that.\n- Cite external evidence when it exists — a Sentry issue ID (`MAILSPRING-CLIENT-AC`), an\n  upstream commit or PR in mailcore2/libetpan/Electron, a spec section, a provider quirk.\n  These justify code that otherwise looks arbitrary and are the most valuable comments in\n  the codebase.\n- Paragraph-length comments should be rare, and almost always attach to a function, class,\n  or module rather than sitting inline. Inline comments belong on one non-obvious line and\n  should be one or two lines long.\n\n## Development Notes\n\n- Reload the window with Developer > Reload: `Cmd+Option+L` (macOS), `Ctrl+Alt+L` (Linux), `Ctrl+Shift+R` (Windows). It reruns renderer code and recompiles edited files (the compile cache is keyed on file contents); main-process code in `app/src/browser` needs a full restart. `Cmd/Ctrl+R` is Reply\n- Dev tools accessible via Menu > Developer > Toggle Developer Tools\n- In dev tools console, `$m` provides access to `mailspring-exports` for debugging\n- Dev mode data is stored separately (e.g., `~/.config/Mailspring-dev/` on Linux)\n- The interface language is set in Preferences > General (`core.intl.language`) and read at launch, so relaunch to apply it. Arabic, Persian, Hebrew and Kurdish run right-to-left: the workspace gets `dir=\"rtl\"` and every stylesheet goes through `rtlcss`\n\n## Claude Hooks\n\nA `PostToolUse` hook in `.claude/settings.json` runs `.claude/hooks/lint-edited-file.sh` after each edit to a `.ts`/`.tsx` file under `app/src` or `app/internal_packages`. It runs `eslint --fix` on that one file and reports anything it couldn't fix.\n",".cursor/mcp.json":"# Model Context Protocol (MCP) Configuration\n\nPath: `.cursor/mcp.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Foundry376/Mailspring/HEAD/.cursor/mcp.json)",".claude-plugin/marketplace.json":"# Claude Plugin Marketplace Catalog\n\nPath: `.claude-plugin/marketplace.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Foundry376/Mailspring/HEAD/.claude-plugin/marketplace.json)",".claude-plugin/plugin.json":"# claude-plugin Plugin Manifest\n\nPath: `.claude-plugin/plugin.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Foundry376/Mailspring/HEAD/.claude-plugin/plugin.json)"},"files":{"CLAUDE.md":"# CLAUDE.md\n\nThis file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.\n\n## Build and Development Commands\n\n```bash\n# Install dependencies\nnpm install\n\n# Run the app in development mode (uses --dev flag, data stored in Mailspring-dev folder)\nnpm start\n\n# Run linting (prettier runs as an eslint rule); lint:check reports without fixing\nnpm run lint\nnpm run lint:check\n\n# TypeScript type checking\nnpm run typecheck\n\n# Run all tests, or only spec files whose path matches a regex\nnpm test\nnpm test -- --spec-file-pattern='calendar'\n\n# Run window-specific tests\nnpm run test-window\n\n# Run Playwright end-to-end tests\nnpm run test:e2e\n\n# TypeScript type checking in watch mode\nnpm run tsc-watch\n\n# Build for production\nnpm run build\n```\n\n## Architecture Overview\n\nMailspring is an Electron-based email client written in TypeScript with React. It uses a plugin architecture where features are implemented as internal packages.\n\n### Key Directories\n\n- **`app/src/`** - Core application source code\n  - `browser/` - Main process code (application lifecycle, window management, auto-updates)\n  - `flux/` - Flux-based state management (actions, stores, models, tasks)\n  - `components/` - Reusable React UI components\n  - `services/` - Application services (search, sanitization, etc.)\n  - `registries/` - Extension registries (components, extensions, database objects)\n  - `global/` - Global exports (`mailspring-exports`, `mailspring-component-kit`)\n\n- **`app/internal_packages/`** - Built-in plugins implementing features (composer, message-list, thread-list, preferences, themes, etc.)\n\n> **IMPORTANT:** Application source code lives in **both** `app/src/` and `app/internal_packages/`. When searching for usages of a module, symbol, or pattern, always search both directories. Searching only `app/src/` will miss a large portion of the codebase and lead to incomplete changes.\n\n- **`app/spec/`** - Jasmine test specs\n\n### Core Modules\n\n**Global exports for plugins:**\n- `mailspring-exports` - Core APIs: Actions, Stores, Models, Tasks, Utils, database access\n- `mailspring-component-kit` - Reusable UI components\n\n**Flux Architecture:**\n- **Models** (`flux/models/`) - Data models: Message, Thread, Contact, Account, Folder, Label, etc.\n- **Stores** (`flux/stores/`) - Application state: DatabaseStore, DraftStore, AccountStore, etc.\n- **Tasks** (`flux/tasks/`) - Async operations: SendDraftTask, ChangeFolderTask, etc.\n- **Actions** (`flux/actions.ts`) - Application-wide action dispatcher\n\n### Plugin Structure\n\nEach plugin in `internal_packages/` has:\n- `package.json` - Metadata with `windowTypes` specifying where plugin loads\n- `lib/main.ts` - Entry point with `activate()` and `deactivate()` lifecycle hooks\n- `lib/` - Plugin source code\n- `styles/` - LESS stylesheets\n- `keymaps/` - Keyboard shortcut definitions\n\n## Core Data Flow: Sync Engine, Tasks, and Observable Database\n\n**Important:** The UI is read-only with respect to the database. All database modifications happen in the C++ sync engine (Mailspring-Sync). The Electron app requests changes via Tasks, and the sync engine streams entity changes back to create a real-time UI.\n\n### Sync Engine Communication (`mailsync-process.ts`, `mailsync-bridge.ts`)\n\nThe sync engine is a separate C++ process spawned per account:\n\n1. **Electron → Sync Engine**: JSON messages sent via stdin (task requests, commands)\n2. **Sync Engine → Electron**: Newline-delimited JSON streamed via stdout (database change deltas)\n\n```\n┌─────────────────┐         stdin (JSON)          ┌──────────────────┐\n│   Electron UI   │ ──────────────────────────────▶│  Mailspring-Sync │\n│  (TypeScript)   │                                │      (C++)       │\n│                 │ ◀────────────────────────────── │                  │\n└─────────────────┘    stdout (JSON deltas)        └──────────────────┘\n```\n\nThe `MailsyncBridge` (in main window only) manages sync process lifecycle, listens to `Actions.queueTask`, and forwards tasks to the appropriate account's sync process.\n\n### Task System (`flux/tasks/`)\n\nTasks represent operations the user wants to perform (send email, star thread, move to folder). They are **persisted models** stored in the database.\n\n**Task Lifecycle:**\n1. UI calls `Actions.queueTask(new SomeTask({...}))`\n2. `MailsyncBridge._onQueueTask()` validates and sends to sync engine via stdin\n3. Sync engine executes the task (local changes + remote API calls)\n4. Sync engine persists task status updates and emits deltas\n5. Task completion triggers `onSuccess()` or `onError()` callbacks\n\n**Task States** (`flux/tasks/task.ts`):\n- `local` - Not yet executed\n- `remote` - Local phase complete, waiting for remote\n- `complete` - Finished successfully\n- `cancelled` - Cancelled before completion\n\n**Key Task Classes:**\n- `SendDraftTask`, `DestroyDraftTask` - Email composition\n- `ChangeLabelsTask`, `ChangeFolderTask` - Organization\n- `ChangeStarredTask`, `ChangeUnreadTask` - Status flags\n- `SyncbackMetadataTask` - Plugin metadata sync\n- `SyncbackEventTask` - Calendar event sync\n\n**Undoable Tasks:**\n\nTasks can support undo/redo by implementing `canBeUndone` and `createUndoTask()`. The `UndoRedoStore` automatically registers tasks with `canBeUndone = true` for undo.\n\nTwo patterns exist:\n1. **Toggle pattern** (`ChangeStarredTask`): Undo simply flips a boolean flag\n2. **Snapshot pattern** (`SyncbackMetadataTask`, `SyncbackEventTask`): Store original state in `undoData`, swap on undo\n\n```typescript\n// Snapshot pattern example\nconst undoData = { ics: event.ics, recurrenceStart: event.recurrenceStart };\nevent.ics = newIcs;  // Modify after capturing\nActions.queueTask(SyncbackEventTask.forUpdating({ event, undoData, description: 'Edit event' }));\n```\n\nSee `docs/undo-redo-task-pattern.md` for detailed implementation guide.\n\n### Task Queue (`flux/stores/task-queue.ts`)\n\nThe TaskQueue store observes Task model changes from the database and provides:\n- `queue()` - Active tasks\n- `completed()` - Finished tasks\n- `waitForPerformLocal(task)` - Promise that resolves when task runs locally\n- `waitForPerformRemote(task)` - Promise that resolves when task fully completes\n\n### Observable Database Pattern\n\n**Database is read-only in Electron** (`flux/stores/database-store.ts`):\n- `DatabaseStore.inTransaction()` throws - writes are not allowed\n- Uses SQLite in WAL mode via better-sqlite3 for concurrent reads\n- The sync engine exclusively handles writes\n\n**Change Records** (`flux/stores/database-change-record.ts`):\n\nWhen the sync engine modifies data, it emits JSON deltas that become `DatabaseChangeRecord` objects:\n```typescript\n{\n  type: 'persist' | 'unpersist',\n  objectClass: 'Thread' | 'Message' | ...,\n  objects: Model[],\n  objectsRawJSON: object[]\n}\n```\n\n**Reactive Queries** (`flux/models/query-subscription.ts`):\n\n`QuerySubscription` provides live-updating query results:\n```typescript\n// Subscribe to all unread threads\nconst subscription = new QuerySubscription(\n  DatabaseStore.findAll(Thread).where({ unread: true })\n);\nsubscription.addCallback((threads) => this.setState({ threads }));\n\n// Subscription automatically updates when DatabaseStore triggers\n```\n\n**Observable Integration** (`Rx.Observable.fromQuery`):\n\nWrap queries as RxJS observables for reactive UI updates:\n```typescript\nRx.Observable.fromQuery(DatabaseStore.findAll(Thread))\n  .subscribe(threads => this.updateUI(threads));\n```\n\n**ObservableListDataSource** (`flux/stores/observable-list-data-source.ts`):\n\nAdapts QuerySubscription for virtualized list components (MultiselectList), supporting:\n- Windowed/paginated data loading\n- Selection state management\n- Automatic updates from database changes\n\n### Data Flow Summary\n\n```\nUser Action → Actions.queueTask() → MailsyncBridge → stdin → Sync Engine\n                                                              │\n                                                              ▼\nUI Updates ← QuerySubscription ← DatabaseStore.trigger() ← stdout deltas\n```\n\n## Comment Style\n\nWrite for a technical reader who prefers self-documenting code. Prefer clearer names and\nsmaller functions over commentary; a comment that restates the code should be deleted.\n\n- State **current behavior and rationale** — why the code is the way it is, and what a\n  reader would otherwise get wrong. Never narrate thought process, stream of\n  consciousness, or the path you took to the answer.\n- Never describe history: where code used to live, what it was folded out of, what the\n  previous implementation did, or what a diff changed. Git records that.\n- Cite external evidence when it exists — a Sentry issue ID (`MAILSPRING-CLIENT-AC`), an\n  upstream commit or PR in mailcore2/libetpan/Electron, a spec section, a provider quirk.\n  These justify code that otherwise looks arbitrary and are the most valuable comments in\n  the codebase.\n- Paragraph-length comments should be rare, and almost always attach to a function, class,\n  or module rather than sitting inline. Inline comments belong on one non-obvious line and\n  should be one or two lines long.\n\n## Development Notes\n\n- Reload the window with Developer > Reload: `Cmd+Option+L` (macOS), `Ctrl+Alt+L` (Linux), `Ctrl+Shift+R` (Windows). It reruns renderer code and recompiles edited files (the compile cache is keyed on file contents); main-process code in `app/src/browser` needs a full restart. `Cmd/Ctrl+R` is Reply\n- Dev tools accessible via Menu > Developer > Toggle Developer Tools\n- In dev tools console, `$m` provides access to `mailspring-exports` for debugging\n- Dev mode data is stored separately (e.g., `~/.config/Mailspring-dev/` on Linux)\n- The interface language is set in Preferences > General (`core.intl.language`) and read at launch, so relaunch to apply it. Arabic, Persian, Hebrew and Kurdish run right-to-left: the workspace gets `dir=\"rtl\"` and every stylesheet goes through `rtlcss`\n\n## Claude Hooks\n\nA `PostToolUse` hook in `.claude/settings.json` runs `.claude/hooks/lint-edited-file.sh` after each edit to a `.ts`/`.tsx` file under `app/src` or `app/internal_packages`. It runs `eslint --fix` on that one file and reports anything it couldn't fix.\n",".cursor/mcp.json":"# Model Context Protocol (MCP) Configuration\n\nPath: `.cursor/mcp.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Foundry376/Mailspring/HEAD/.cursor/mcp.json)",".claude-plugin/marketplace.json":"# Claude Plugin Marketplace Catalog\n\nPath: `.claude-plugin/marketplace.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Foundry376/Mailspring/HEAD/.claude-plugin/marketplace.json)",".claude-plugin/plugin.json":"# claude-plugin Plugin Manifest\n\nPath: `.claude-plugin/plugin.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Foundry376/Mailspring/HEAD/.claude-plugin/plugin.json)"},"items":[{"name":"CLAUDE.md","path":"CLAUDE.md","rawUrl":"https://raw.githubusercontent.com/Foundry376/Mailspring/HEAD/CLAUDE.md","title":"Claude Agent Guidelines & System Prompt","category":"claude-rule","format":"markdown","content":"# CLAUDE.md\n\nThis file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.\n\n## Build and Development Commands\n\n```bash\n# Install dependencies\nnpm install\n\n# Run the app in development mode (uses --dev flag, data stored in Mailspring-dev folder)\nnpm start\n\n# Run linting (prettier runs as an eslint rule); lint:check reports without fixing\nnpm run lint\nnpm run lint:check\n\n# TypeScript type checking\nnpm run typecheck\n\n# Run all tests, or only spec files whose path matches a regex\nnpm test\nnpm test -- --spec-file-pattern='calendar'\n\n# Run window-specific tests\nnpm run test-window\n\n# Run Playwright end-to-end tests\nnpm run test:e2e\n\n# TypeScript type checking in watch mode\nnpm run tsc-watch\n\n# Build for production\nnpm run build\n```\n\n## Architecture Overview\n\nMailspring is an Electron-based email client written in TypeScript with React. It uses a plugin architecture where features are implemented as internal packages.\n\n### Key Directories\n\n- **`app/src/`** - Core application source code\n  - `browser/` - Main process code (application lifecycle, window management, auto-updates)\n  - `flux/` - Flux-based state management (actions, stores, models, tasks)\n  - `components/` - Reusable React UI components\n  - `services/` - Application services (search, sanitization, etc.)\n  - `registries/` - Extension registries (components, extensions, database objects)\n  - `global/` - Global exports (`mailspring-exports`, `mailspring-component-kit`)\n\n- **`app/internal_packages/`** - Built-in plugins implementing features (composer, message-list, thread-list, preferences, themes, etc.)\n\n> **IMPORTANT:** Application source code lives in **both** `app/src/` and `app/internal_packages/`. When searching for usages of a module, symbol, or pattern, always search both directories. Searching only `app/src/` will miss a large portion of the codebase and lead to incomplete changes.\n\n- **`app/spec/`** - Jasmine test specs\n\n### Core Modules\n\n**Global exports for plugins:**\n- `mailspring-exports` - Core APIs: Actions, Stores, Models, Tasks, Utils, database access\n- `mailspring-component-kit` - Reusable UI components\n\n**Flux Architecture:**\n- **Models** (`flux/models/`) - Data models: Message, Thread, Contact, Account, Folder, Label, etc.\n- **Stores** (`flux/stores/`) - Application state: DatabaseStore, DraftStore, AccountStore, etc.\n- **Tasks** (`flux/tasks/`) - Async operations: SendDraftTask, ChangeFolderTask, etc.\n- **Actions** (`flux/actions.ts`) - Application-wide action dispatcher\n\n### Plugin Structure\n\nEach plugin in `internal_packages/` has:\n- `package.json` - Metadata with `windowTypes` specifying where plugin loads\n- `lib/main.ts` - Entry point with `activate()` and `deactivate()` lifecycle hooks\n- `lib/` - Plugin source code\n- `styles/` - LESS stylesheets\n- `keymaps/` - Keyboard shortcut definitions\n\n## Core Data Flow: Sync Engine, Tasks, and Observable Database\n\n**Important:** The UI is read-only with respect to the database. All database modifications happen in the C++ sync engine (Mailspring-Sync). The Electron app requests changes via Tasks, and the sync engine streams entity changes back to create a real-time UI.\n\n### Sync Engine Communication (`mailsync-process.ts`, `mailsync-bridge.ts`)\n\nThe sync engine is a separate C++ process spawned per account:\n\n1. **Electron → Sync Engine**: JSON messages sent via stdin (task requests, commands)\n2. **Sync Engine → Electron**: Newline-delimited JSON streamed via stdout (database change deltas)\n\n```\n┌─────────────────┐         stdin (JSON)          ┌──────────────────┐\n│   Electron UI   │ ──────────────────────────────▶│  Mailspring-Sync │\n│  (TypeScript)   │                                │      (C++)       │\n│                 │ ◀────────────────────────────── │                  │\n└─────────────────┘    stdout (JSON deltas)        └──────────────────┘\n```\n\nThe `MailsyncBridge` (in main window only) manages sync process lifecycle, listens to `Actions.queueTask`, and forwards tasks to the appropriate account's sync process.\n\n### Task System (`flux/tasks/`)\n\nTasks represent operations the user wants to perform (send email, star thread, move to folder). They are **persisted models** stored in the database.\n\n**Task Lifecycle:**\n1. UI calls `Actions.queueTask(new SomeTask({...}))`\n2. `MailsyncBridge._onQueueTask()` validates and sends to sync engine via stdin\n3. Sync engine executes the task (local changes + remote API calls)\n4. Sync engine persists task status updates and emits deltas\n5. Task completion triggers `onSuccess()` or `onError()` callbacks\n\n**Task States** (`flux/tasks/task.ts`):\n- `local` - Not yet executed\n- `remote` - Local phase complete, waiting for remote\n- `complete` - Finished successfully\n- `cancelled` - Cancelled before completion\n\n**Key Task Classes:**\n- `SendDraftTask`, `DestroyDraftTask` - Email composition\n- `ChangeLabelsTask`, `ChangeFolderTask` - Organization\n- `ChangeStarredTask`, `ChangeUnreadTask` - Status flags\n- `SyncbackMetadataTask` - Plugin metadata sync\n- `SyncbackEventTask` - Calendar event sync\n\n**Undoable Tasks:**\n\nTasks can support undo/redo by implementing `canBeUndone` and `createUndoTask()`. The `UndoRedoStore` automatically registers tasks with `canBeUndone = true` for undo.\n\nTwo patterns exist:\n1. **Toggle pattern** (`ChangeStarredTask`): Undo simply flips a boolean flag\n2. **Snapshot pattern** (`SyncbackMetadataTask`, `SyncbackEventTask`): Store original state in `undoData`, swap on undo\n\n```typescript\n// Snapshot pattern example\nconst undoData = { ics: event.ics, recurrenceStart: event.recurrenceStart };\nevent.ics = newIcs;  // Modify after capturing\nActions.queueTask(SyncbackEventTask.forUpdating({ event, undoData, description: 'Edit event' }));\n```\n\nSee `docs/undo-redo-task-pattern.md` for detailed implementation guide.\n\n### Task Queue (`flux/stores/task-queue.ts`)\n\nThe TaskQueue store observes Task model changes from the database and provides:\n- `queue()` - Active tasks\n- `completed()` - Finished tasks\n- `waitForPerformLocal(task)` - Promise that resolves when task runs locally\n- `waitForPerformRemote(task)` - Promise that resolves when task fully completes\n\n### Observable Database Pattern\n\n**Database is read-only in Electron** (`flux/stores/database-store.ts`):\n- `DatabaseStore.inTransaction()` throws - writes are not allowed\n- Uses SQLite in WAL mode via better-sqlite3 for concurrent reads\n- The sync engine exclusively handles writes\n\n**Change Records** (`flux/stores/database-change-record.ts`):\n\nWhen the sync engine modifies data, it emits JSON deltas that become `DatabaseChangeRecord` objects:\n```typescript\n{\n  type: 'persist' | 'unpersist',\n  objectClass: 'Thread' | 'Message' | ...,\n  objects: Model[],\n  objectsRawJSON: object[]\n}\n```\n\n**Reactive Queries** (`flux/models/query-subscription.ts`):\n\n`QuerySubscription` provides live-updating query results:\n```typescript\n// Subscribe to all unread threads\nconst subscription = new QuerySubscription(\n  DatabaseStore.findAll(Thread).where({ unread: true })\n);\nsubscription.addCallback((threads) => this.setState({ threads }));\n\n// Subscription automatically updates when DatabaseStore triggers\n```\n\n**Observable Integration** (`Rx.Observable.fromQuery`):\n\nWrap queries as RxJS observables for reactive UI updates:\n```typescript\nRx.Observable.fromQuery(DatabaseStore.findAll(Thread))\n  .subscribe(threads => this.updateUI(threads));\n```\n\n**ObservableListDataSource** (`flux/stores/observable-list-data-source.ts`):\n\nAdapts QuerySubscription for virtualized list components (MultiselectList), supporting:\n- Windowed/paginated data loading\n- Selection state management\n- Automatic updates from database changes\n\n### Data Flow Summary\n\n```\nUser Action → Actions.queueTask() → MailsyncBridge → stdin → Sync Engine\n                                                              │\n                                                              ▼\nUI Updates ← QuerySubscription ← DatabaseStore.trigger() ← stdout deltas\n```\n\n## Comment Style\n\nWrite for a technical reader who prefers self-documenting code. Prefer clearer names and\nsmaller functions over commentary; a comment that restates the code should be deleted.\n\n- State **current behavior and rationale** — why the code is the way it is, and what a\n  reader would otherwise get wrong. Never narrate thought process, stream of\n  consciousness, or the path you took to the answer.\n- Never describe history: where code used to live, what it was folded out of, what the\n  previous implementation did, or what a diff changed. Git records that.\n- Cite external evidence when it exists — a Sentry issue ID (`MAILSPRING-CLIENT-AC`), an\n  upstream commit or PR in mailcore2/libetpan/Electron, a spec section, a provider quirk.\n  These justify code that otherwise looks arbitrary and are the most valuable comments in\n  the codebase.\n- Paragraph-length comments should be rare, and almost always attach to a function, class,\n  or module rather than sitting inline. Inline comments belong on one non-obvious line and\n  should be one or two lines long.\n\n## Development Notes\n\n- Reload the window with Developer > Reload: `Cmd+Option+L` (macOS), `Ctrl+Alt+L` (Linux), `Ctrl+Shift+R` (Windows). It reruns renderer code and recompiles edited files (the compile cache is keyed on file contents); main-process code in `app/src/browser` needs a full restart. `Cmd/Ctrl+R` is Reply\n- Dev tools accessible via Menu > Developer > Toggle Developer Tools\n- In dev tools console, `$m` provides access to `mailspring-exports` for debugging\n- Dev mode data is stored separately (e.g., `~/.config/Mailspring-dev/` on Linux)\n- The interface language is set in Preferences > General (`core.intl.language`) and read at launch, so relaunch to apply it. Arabic, Persian, Hebrew and Kurdish run right-to-left: the workspace gets `dir=\"rtl\"` and every stylesheet goes through `rtlcss`\n\n## Claude Hooks\n\nA `PostToolUse` hook in `.claude/settings.json` runs `.claude/hooks/lint-edited-file.sh` after each edit to a `.ts`/`.tsx` file under `app/src` or `app/internal_packages`. It runs `eslint --fix` on that one file and reports anything it couldn't fix.\n","isInternal":false,"tokens":2362,"sizeBytes":10475},{"name":"marketplace.json","path":".claude-plugin/marketplace.json","rawUrl":"https://raw.githubusercontent.com/Foundry376/Mailspring/HEAD/.claude-plugin/marketplace.json","title":"Claude Plugin Marketplace Catalog","category":"marketplace","format":"json","content":"# Claude Plugin Marketplace Catalog\n\nPath: `.claude-plugin/marketplace.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Foundry376/Mailspring/HEAD/.claude-plugin/marketplace.json)","tokens":300,"sizeBytes":200},{"name":"plugin.json","path":".claude-plugin/plugin.json","rawUrl":"https://raw.githubusercontent.com/Foundry376/Mailspring/HEAD/.claude-plugin/plugin.json","title":"claude-plugin Plugin Manifest","category":"plugin-manifest","format":"json","content":"# claude-plugin Plugin Manifest\n\nPath: `.claude-plugin/plugin.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Foundry376/Mailspring/HEAD/.claude-plugin/plugin.json)","tokens":300,"sizeBytes":186},{"name":"mcp.json","path":".cursor/mcp.json","rawUrl":"https://raw.githubusercontent.com/Foundry376/Mailspring/HEAD/.cursor/mcp.json","title":"Model Context Protocol (MCP) Configuration","category":"mcp-config","format":"json","content":"# Model Context Protocol (MCP) Configuration\n\nPath: `.cursor/mcp.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Foundry376/Mailspring/HEAD/.cursor/mcp.json)","tokens":300,"sizeBytes":179}],"systemPromptSnippet":"<yakaai_skills repo=\"Foundry376/Mailspring\">\n<!-- File: CLAUDE.md (Tokens: ~2362 | Category: claude-rule) -->\n# CLAUDE.md\n\nThis file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.\n\n## Build and Development Commands\n\n```bash\n# Install dependencies\nnpm install\n\n# Run the app in development mode (uses --dev flag, data stored in Mailspring-dev folder)\nnpm start\n\n# Run linting (prettier runs as an eslint rule); lint:check reports without fixing\nnpm run lint\nnpm run lint:check\n\n# TypeScript type checking\nnpm run typecheck\n\n# Run all tests, or only spec files whose path matches a regex\nnpm test\nnpm test -- --spec-file-pattern='calendar'\n\n# Run window-specific tests\nnpm run test-window\n\n# Run Playwright end-to-end tests\nnpm run test:e2e\n\n# TypeScript type checking in watch mode\nnpm run tsc-watch\n\n# Build for production\nnpm run build\n```\n\n## Architecture Overview\n\nMailspring is an Electron-based email client written in TypeScript with React. It uses a plugin architecture where features are implemented as internal packages.\n\n### Key Directories\n\n- **`app/src/`** - Core application source code\n  - `browser/` - Main process code (application lifecycle, window management, auto-updates)\n  - `flux/` - Flux-based state management (actions, stores, models, tasks)\n  - `components/` - Reusable React UI components\n  - `services/` - Application services (search, sanitization, etc.)\n  - `registries/` - Extension registries (components, extensions, database objects)\n  - `global/` - Global exports (`mailspring-exports`, `mailspring-component-kit`)\n\n- **`app/internal_packages/`** - Built-in plugins implementing features (composer, message-list, thread-list, preferences, themes, etc.)\n\n> **IMPORTANT:** Application source code lives in **both** `app/src/` and `app/internal_packages/`. When searching for usages of a module, symbol, or pattern, always search both directories. Searching only `app/src/` will miss a large portion of the codebase and lead to incomplete changes.\n\n- **`app/spec/`** - Jasmine test specs\n\n### Core Modules\n\n**Global exports for plugins:**\n- `mailspring-exports` - Core APIs: Actions, Stores, Models, Tasks, Utils, database access\n- `mailspring-component-kit` - Reusable UI components\n\n**Flux Architecture:**\n- **Models** (`flux/models/`) - Data models: Message, Thread, Contact, Account, Folder, Label, etc.\n- **Stores** (`flux/stores/`) - Application state: DatabaseStore, DraftStore, AccountStore, etc.\n- **Tasks** (`flux/tasks/`) - Async operations: SendDraftTask, ChangeFolderTask, etc.\n- **Actions** (`flux/actions.ts`) - Application-wide action dispatcher\n\n### Plugin Structure\n\nEach plugin in `internal_packages/` has:\n- `package.json` - Metadata with `windowTypes` specifying where plugin loads\n- `lib/main.ts` - Entry point with `activate()` and `deactivate()` lifecycle hooks\n- `lib/` - Plugin source code\n- `styles/` - LESS stylesheets\n- `keymaps/` - Keyboard shortcut definitions\n\n## Core Data Flow: Sync Engine, Tasks, and Observable Database\n\n**Important:** The UI is read-only with respect to the database. All database modifications happen in the C++ sync engine (Mailspring-Sync). The Electron app requests changes via Tasks, and the sync engine streams entity changes back to create a real-time UI.\n\n### Sync Engine Communication (`mailsync-process.ts`, `mailsync-bridge.ts`)\n\nThe sync engine is a separate C++ process spawned per account:\n\n1. **Electron → Sync Engine**: JSON messages sent via stdin (task requests, commands)\n2. **Sync Engine → Electron**: Newline-delimited JSON streamed via stdout (database change deltas)\n\n```\n┌─────────────────┐         stdin (JSON)          ┌──────────────────┐\n│   Electron UI   │ ──────────────────────────────▶│  Mailspring-Sync │\n│  (TypeScript)   │                                │      (C++)       │\n│                 │ ◀────────────────────────────── │                  │\n└─────────────────┘    stdout (JSON deltas)        └──────────────────┘\n```\n\nThe `MailsyncBridge` (in main window only) manages sync process lifecycle, listens to `Actions.queueTask`, and forwards tasks to the appropriate account's sync process.\n\n### Task System (`flux/tasks/`)\n\nTasks represent operations the user wants to perform (send email, star thread, move to folder). They are **persisted models** stored in the database.\n\n**Task Lifecycle:**\n1. UI calls `Actions.queueTask(new SomeTask({...}))`\n2. `MailsyncBridge._onQueueTask()` validates and sends to sync engine via stdin\n3. Sync engine executes the task (local changes + remote API calls)\n4. Sync engine persists task status updates and emits deltas\n5. Task completion triggers `onSuccess()` or `onError()` callbacks\n\n**Task States** (`flux/tasks/task.ts`):\n- `local` - Not yet executed\n- `remote` - Local phase complete, waiting for remote\n- `complete` - Finished successfully\n- `cancelled` - Cancelled before completion\n\n**Key Task Classes:**\n- `SendDraftTask`, `DestroyDraftTask` - Email composition\n- `ChangeLabelsTask`, `ChangeFolderTask` - Organization\n- `ChangeStarredTask`, `ChangeUnreadTask` - Status flags\n- `SyncbackMetadataTask` - Plugin metadata sync\n- `SyncbackEventTask` - Calendar event sync\n\n**Undoable Tasks:**\n\nTasks can support undo/redo by implementing `canBeUndone` and `createUndoTask()`. The `UndoRedoStore` automatically registers tasks with `canBeUndone = true` for undo.\n\nTwo patterns exist:\n1. **Toggle pattern** (`ChangeStarredTask`): Undo simply flips a boolean flag\n2. **Snapshot pattern** (`SyncbackMetadataTask`, `SyncbackEventTask`): Store original state in `undoData`, swap on undo\n\n```typescript\n// Snapshot pattern example\nconst undoData = { ics: event.ics, recurrenceStart: event.recurrenceStart };\nevent.ics = newIcs;  // Modify after capturing\nActions.queueTask(SyncbackEventTask.forUpdating({ event, undoData, description: 'Edit event' }));\n```\n\nSee `docs/undo-redo-task-pattern.md` for detailed implementation guide.\n\n### Task Queue (`flux/stores/task-queue.ts`)\n\nThe TaskQueue store observes Task model changes from the database and provides:\n- `queue()` - Active tasks\n- `completed()` - Finished tasks\n- `waitForPerformLocal(task)` - Promise that resolves when task runs locally\n- `waitForPerformRemote(task)` - Promise that resolves when task fully completes\n\n### Observable Database Pattern\n\n**Database is read-only in Electron** (`flux/stores/database-store.ts`):\n- `DatabaseStore.inTransaction()` throws - writes are not allowed\n- Uses SQLite in WAL mode via better-sqlite3 for concurrent reads\n- The sync engine exclusively handles writes\n\n**Change Records** (`flux/stores/database-change-record.ts`):\n\nWhen the sync engine modifies data, it emits JSON deltas that become `DatabaseChangeRecord` objects:\n```typescript\n{\n  type: 'persist' | 'unpersist',\n  objectClass: 'Thread' | 'Message' | ...,\n  objects: Model[],\n  objectsRawJSON: object[]\n}\n```\n\n**Reactive Queries** (`flux/models/query-subscription.ts`):\n\n`QuerySubscription` provides live-updating query results:\n```typescript\n// Subscribe to all unread threads\nconst subscription = new QuerySubscription(\n  DatabaseStore.findAll(Thread).where({ unread: true })\n);\nsubscription.addCallback((threads) => this.setState({ threads }));\n\n// Subscription automatically updates when DatabaseStore triggers\n```\n\n**Observable Integration** (`Rx.Observable.fromQuery`):\n\nWrap queries as RxJS observables for reactive UI updates:\n```typescript\nRx.Observable.fromQuery(DatabaseStore.findAll(Thread))\n  .subscribe(threads => this.updateUI(threads));\n```\n\n**ObservableListDataSource** (`flux/stores/observable-list-data-source.ts`):\n\nAdapts QuerySubscription for virtualized list components (MultiselectList), supporting:\n- Windowed/paginated data loading\n- Selection state management\n- Automatic updates from database changes\n\n### Data Flow Summary\n\n```\nUser Action → Actions.queueTask() → MailsyncBridge → stdin → Sync Engine\n                                                              │\n                                                              ▼\nUI Updates ← QuerySubscription ← DatabaseStore.trigger() ← stdout deltas\n```\n\n## Comment Style\n\nWrite for a technical reader who prefers self-documenting code. Prefer clearer names and\nsmaller functions over commentary; a comment that restates the code should be deleted.\n\n- State **current behavior and rationale** — why the code is the way it is, and what a\n  reader would otherwise get wrong. Never narrate thought process, stream of\n  consciousness, or the path you took to the answer.\n- Never describe history: where code used to live, what it was folded out of, what the\n  previous implementation did, or what a diff changed. Git records that.\n- Cite external evidence when it exists — a Sentry issue ID (`MAILSPRING-CLIENT-AC`), an\n  upstream commit or PR in mailcore2/libetpan/Electron, a spec section, a provider quirk.\n  These justify code that otherwise looks arbitrary and are the most valuable comments in\n  the codebase.\n- Paragraph-length comments should be rare, and almost always attach to a function, class,\n  or module rather than sitting inline. Inline comments belong on one non-obvious line and\n  should be one or two lines long.\n\n## Development Notes\n\n- Reload the window with Developer > Reload: `Cmd+Option+L` (macOS), `Ctrl+Alt+L` (Linux), `Ctrl+Shift+R` (Windows). It reruns renderer code and recompiles edited files (the compile cache is keyed on file contents); main-process code in `app/src/browser` needs a full restart. `Cmd/Ctrl+R` is Reply\n- Dev tools accessible via Menu > Developer > Toggle Developer Tools\n- In dev tools console, `$m` provides access to `mailspring-exports` for debugging\n- Dev mode data is stored separately (e.g., `~/.config/Mailspring-dev/` on Linux)\n- The interface language is set in Preferences > General (`core.intl.language`) and read at launch, so relaunch to apply it. Arabic, Persian, Hebrew and Kurdish run right-to-left: the workspace gets `dir=\"rtl\"` and every stylesheet goes through `rtlcss`\n\n## Claude Hooks\n\nA `PostToolUse` hook in `.claude/settings.json` runs `.claude/hooks/lint-edited-file.sh` after each edit to a `.ts`/`.tsx` file under `app/src` or `app/internal_packages`. It runs `eslint --fix` on that one file and reports anything it couldn't fix.\n\n<!-- File: .claude-plugin/marketplace.json (Tokens: ~300 | Category: marketplace) -->\n# Claude Plugin Marketplace Catalog\n\nPath: `.claude-plugin/marketplace.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Foundry376/Mailspring/HEAD/.claude-plugin/marketplace.json)\n\n<!-- File: .claude-plugin/plugin.json (Tokens: ~300 | Category: plugin-manifest) -->\n# claude-plugin Plugin Manifest\n\nPath: `.claude-plugin/plugin.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Foundry376/Mailspring/HEAD/.claude-plugin/plugin.json)\n\n<!-- File: .cursor/mcp.json (Tokens: ~300 | Category: mcp-config) -->\n# Model Context Protocol (MCP) Configuration\n\nPath: `.cursor/mcp.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/Foundry376/Mailspring/HEAD/.cursor/mcp.json)\n</yakaai_skills>"}