diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1fe024a4..83208b18 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -7,17 +7,20 @@ Thank you for your interest in contributing to Bulwark Webmail! This document pr ### Development Setup 1. **Fork and clone** the repository: + ```bash git clone https://github.com/bulwarkmail/webmail.git cd webmail ``` 2. **Install dependencies**: + ```bash npm install ``` 3. **Set up environment**: + ```bash cp .env.example .env.local # Edit .env.local with your JMAP server URL @@ -75,9 +78,10 @@ This project uses **next-intl** for internationalization. Please follow these gu ### Key Rules 1. **Never hardcode user-facing text** - Always use translations: + ```tsx - const t = useTranslations('namespace'); - return
{t('key')}
; + const t = useTranslations("namespace"); + return
{t("key")}
; ``` 2. **Translation file locations**: @@ -109,6 +113,7 @@ This project uses **next-intl** for internationalization. Please follow these gu ### Before Submitting 1. **Create a feature branch**: + ```bash git checkout -b feature/your-feature-name ``` @@ -147,6 +152,7 @@ Follow the conventional commits format: - `chore:` - Maintenance tasks Examples: + ``` feat: add email threading support fix: resolve attachment download issue @@ -184,6 +190,7 @@ webmail/ ## Questions? If you have questions about contributing, feel free to: + - Open an issue for discussion - Check existing issues and pull requests diff --git a/README.md b/README.md index 3618c254..0e546c3e 100644 --- a/README.md +++ b/README.md @@ -200,6 +200,7 @@ OAUTH_ISSUER_URL= # optional, for external IdPs (Keycloak, Authe ``` Endpoints are auto-discovered via `.well-known/oauth-authorization-server` or `.well-known/openid-configuration`. +
@@ -210,35 +211,36 @@ SESSION_SECRET=your-secret-key # Generate with: openssl rand -base64 32 ``` Credentials encrypted with AES-256-GCM, stored in an httpOnly cookie (30-day expiry). +
## Keyboard Shortcuts -| Key | Action | -|-----|--------| -| `j` / `k` | Navigate between emails | -| `Enter` / `o` | Open email | -| `Esc` | Close / deselect | -| `c` | Compose | -| `r` / `R` | Reply / Reply all | -| `f` | Forward | -| `s` | Star | -| `e` | Archive | -| `#` | Delete | -| `/` | Search | -| `?` | Show all shortcuts | +| Key | Action | +| ------------- | ----------------------- | +| `j` / `k` | Navigate between emails | +| `Enter` / `o` | Open email | +| `Esc` | Close / deselect | +| `c` | Compose | +| `r` / `R` | Reply / Reply all | +| `f` | Forward | +| `s` | Star | +| `e` | Archive | +| `#` | Delete | +| `/` | Search | +| `?` | Show all shortcuts | ## Tech Stack -| | | -|---|---| +| | | +| ------------- | ------------------------------------------------- | | **Framework** | [Next.js 16](https://nextjs.org/) with App Router | -| **Language** | TypeScript | -| **Styling** | [Tailwind CSS v4](https://tailwindcss.com/) | -| **State** | [Zustand](https://zustand-demo.pmnd.rs/) | -| **Protocol** | Custom JMAP client (RFC 8620) | -| **i18n** | [next-intl](https://next-intl-docs.vercel.app/) | -| **Icons** | [Lucide React](https://lucide.dev/) | +| **Language** | TypeScript | +| **Styling** | [Tailwind CSS v4](https://tailwindcss.com/) | +| **State** | [Zustand](https://zustand-demo.pmnd.rs/) | +| **Protocol** | Custom JMAP client (RFC 8620) | +| **i18n** | [next-intl](https://next-intl-docs.vercel.app/) | +| **Icons** | [Lucide React](https://lucide.dev/) | ## Why Stalwart? diff --git a/ROADMAP.md b/ROADMAP.md index bdd2fbec..675ea31a 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -5,12 +5,14 @@ This document tracks the development status and planned features for Bulwark Web ## Completed Features ### Core Infrastructure + - [x] Next.js 16 with TypeScript and App Router - [x] Tailwind CSS v4 with Oxide engine - [x] Zustand state management - [x] Custom JMAP client implementation (RFC 8620) ### Authentication + - [x] Login with JMAP server authentication - [x] Session management (no password storage for security) - [x] Username autocomplete with history @@ -23,6 +25,7 @@ This document tracks the development status and planned features for Bulwark Web - [x] "Remember me" session persistence for Basic Auth (AES-256-GCM encrypted httpOnly cookie) ### JMAP Server Connection + - [x] Session establishment and keep-alive - [x] Connection error handling and retries - [x] CORS error detection with actionable user guidance @@ -32,6 +35,7 @@ This document tracks the development status and planned features for Bulwark Web - [x] Shared folders support (multi-account access) ### Email Operations + - [x] Email fetching and display - [x] Full HTML email rendering - [x] Compose, reply, reply-all, forward @@ -48,6 +52,7 @@ This document tracks the development status and planned features for Bulwark Web - [x] Email threading (Gmail-style inline expansion) ### Real-time Updates + - [x] EventSource for JMAP push notifications - [x] State synchronization - [x] Email arrival notifications @@ -55,6 +60,7 @@ This document tracks the development status and planned features for Bulwark Web - [x] Mailbox change handling ### User Interface + - [x] Three-pane layout (sidebar, list, viewer) - [x] Minimalist design system - [x] Dark and light theme support @@ -86,6 +92,7 @@ This document tracks the development status and planned features for Bulwark Web - [x] Screen reader live region announcements (sr-only) ### Internationalization + - [x] English language support - [x] French language support - [x] Japanese language support @@ -98,6 +105,7 @@ This document tracks the development status and planned features for Bulwark Web - [x] Language preference persistence ### Security & Accessibility + - [x] External content blocked by default - [x] HTML sanitization with DOMPurify - [x] User control for loading external content @@ -114,6 +122,7 @@ This document tracks the development status and planned features for Bulwark Web - [x] Screen reader sr-only live region for dynamic announcements ### Identity Management + - [x] Multiple sender identities (name, email, signature) - [x] Sub-addressing support (user+tag@domain.com) - [x] Per-identity signatures @@ -123,6 +132,7 @@ This document tracks the development status and planned features for Bulwark Web - [x] Primary identity (matching login) selected by default in composer ### Address Book & Contacts + - [x] Contact store with JMAP sync and local fallback - [x] Contact CRUD operations (create, read, update, delete) - [x] Contacts list view with search/filter @@ -136,12 +146,14 @@ This document tracks the development status and planned features for Bulwark Web - [x] i18n support for contacts (all 8 languages) ### Vacation Responder + - [x] JMAP VacationResponse singleton management - [x] Settings tab with date range and message configuration - [x] Sidebar indicator when vacation auto-reply is active - [x] i18n support (all 8 languages) ### Calendar Integration + - [x] JMAP Calendar types (RFC 8984) and client methods - [x] Calendar capability detection (urn:ietf:params:jmap:calendars) - [x] Calendar store with Zustand (persist middleware) @@ -177,6 +189,7 @@ This document tracks the development status and planned features for Bulwark Web - [x] Event duplication button in modal (clones event +1 day, opens for editing) ### Email Filters + - [x] JMAP Sieve Scripts (RFC 9661) with capability detection - [x] Visual rule builder (conditions: From/To/Cc/Subject/Header/Size/Body, actions: Move/Copy/Forward/Mark read/Star/Label/Discard/Reject/Keep/Stop) - [x] Raw Sieve script editor with syntax validation @@ -189,6 +202,7 @@ This document tracks the development status and planned features for Bulwark Web - [x] i18n support (all 8 languages) ### Email Templates + - [x] Reusable email templates with local storage persistence - [x] Category organization (General, Business, Personal, Support, Follow-up, custom) - [x] Dynamic placeholder variables with auto-fill from composer context @@ -200,10 +214,12 @@ This document tracks the development status and planned features for Bulwark Web - [x] i18n support (all 8 languages) ### Email Display + - [x] Proper email layout without horizontal scroll or clipping - [x] Blocked image container collapsing (no empty spaces in newsletters) ### Testing + - [x] Unit tests for validation utilities (57 tests) - [x] Unit tests for email sanitization (27 tests) - [x] Unit tests for color transformation (40 tests) @@ -226,6 +242,7 @@ This document tracks the development status and planned features for Bulwark Web - [x] Playwright E2E framework setup ### Deployment + - [x] Runtime environment variables (Docker-friendly configuration) - [x] Health check endpoint - [x] Docker support (multi-stage build, docker-compose, standalone output) @@ -238,26 +255,31 @@ This document tracks the development status and planned features for Bulwark Web ## Planned Features ### Advanced Features + - [ ] Free/busy queries (Principal/getAvailability) - [ ] Calendar sharing UI (JMAP Sharing RFC 9670) - [ ] Email encryption (PGP/GPG) ### Performance Optimizations + - [ ] Email content caching - [ ] Bundle size optimization - [ ] Service worker for offline support - [ ] Lazy loading for attachments ### Testing (Remaining) + - [ ] E2E tests with real JMAP server - [ ] Accessibility testing - [ ] Performance testing ### Deployment + - [ ] Production build optimizations - [ ] Monitoring and logging ### Security Enhancements + - [ ] Rate limiting ## Known Issues diff --git a/docker-compose.yml b/docker-compose.yml index 5de56760..733c3645 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -6,7 +6,15 @@ services: env_file: - .env.local healthcheck: - test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:3000/api/health"] + test: + [ + "CMD", + "wget", + "--no-verbose", + "--tries=1", + "--spider", + "http://127.0.0.1:3000/api/health", + ] interval: 30s timeout: 5s retries: 3 diff --git a/specifications/addon-plugin-theme-concept.md b/specifications/addon-plugin-theme-concept.md index b67e21ee..18099da2 100644 --- a/specifications/addon-plugin-theme-concept.md +++ b/specifications/addon-plugin-theme-concept.md @@ -36,13 +36,13 @@ This document describes a system that allows the Bulwark Webmail application to ### Rebuild Requirements -| Addon Type | Source | Rebuild Needed? | -|-----------|--------|----------------| -| Theme | Bundled (`/addons/themes/`) | **Yes** — included at build time | -| Theme | URL (remote) | **No** — CSS loaded via `` at runtime | -| Plugin | Bundled (`/addons/plugins/`) | **Yes** — included at build time | -| Plugin | URL (remote) | **No** — JS loaded at runtime (see §6.3) | -| Plugin | Local file (dev mode) | **No** — loaded at runtime | +| Addon Type | Source | Rebuild Needed? | +| ---------- | ---------------------------- | ------------------------------------------- | +| Theme | Bundled (`/addons/themes/`) | **Yes** — included at build time | +| Theme | URL (remote) | **No** — CSS loaded via `` at runtime | +| Plugin | Bundled (`/addons/plugins/`) | **Yes** — included at build time | +| Plugin | URL (remote) | **No** — JS loaded at runtime (see §6.3) | +| Plugin | Local file (dev mode) | **No** — loaded at runtime | Bundled addons are compiled into the app's static assets during `next build`. Adding, removing, or updating a bundled addon requires a rebuild and redeploy. URL-based and local addons are fully runtime-loaded — users can install, enable, disable, and uninstall them without any rebuild. @@ -56,14 +56,14 @@ Bundled addons are compiled into the app's static assets during `next build`. Ad ## 2. Terminology -| Term | Definition | -|------|-----------| -| **Addon** | Any installable extension — umbrella term for themes and plugins. | -| **Theme** | An addon that only customizes visual appearance (colors, fonts, spacing, density). Ships as CSS + a manifest. Contains no executable code. | -| **Plugin** | An addon that adds or modifies functionality. Ships as a JS/TS module + a manifest. May include a theme. | -| **Slot** | A named insertion point in the UI where plugins can render components. | -| **Hook Point** | A named event or state transition where plugins can run logic. | -| **Manifest** | A `addon.json` file that declares metadata, permissions, and extension points. | +| Term | Definition | +| -------------- | ------------------------------------------------------------------------------------------------------------------------------------------ | +| **Addon** | Any installable extension — umbrella term for themes and plugins. | +| **Theme** | An addon that only customizes visual appearance (colors, fonts, spacing, density). Ships as CSS + a manifest. Contains no executable code. | +| **Plugin** | An addon that adds or modifies functionality. Ships as a JS/TS module + a manifest. May include a theme. | +| **Slot** | A named insertion point in the UI where plugins can render components. | +| **Hook Point** | A named event or state transition where plugins can run logic. | +| **Manifest** | A `addon.json` file that declares metadata, permissions, and extension points. | --- @@ -74,65 +74,59 @@ Every addon has an `addon.json` at its root: ```jsonc { // ── Identity ── - "id": "com.example.my-addon", // Reverse-domain unique ID + "id": "com.example.my-addon", // Reverse-domain unique ID "name": "My Addon", - "version": "1.0.0", // Semver + "version": "1.0.0", // Semver "description": "A brief description.", "author": { "name": "Jane Doe", - "url": "https://example.com" + "url": "https://example.com", }, "license": "MIT", "homepage": "https://example.com/my-addon", // ── Compatibility ── "engine": { - "webmail": ">=1.0.0" // Required host app version range + "webmail": ">=1.0.0", // Required host app version range }, // ── Type ── - "type": "plugin", // "theme" | "plugin" + "type": "plugin", // "theme" | "plugin" // ── Entry Points (plugins only) ── - "main": "dist/index.js", // Plugin entry module - "styles": "dist/styles.css", // Optional supplementary CSS + "main": "dist/index.js", // Plugin entry module + "styles": "dist/styles.css", // Optional supplementary CSS // ── Theme Definition (themes, or plugins that include a theme) ── "theme": { - "variables": "theme.css", // CSS file with variable overrides - "presets": ["light", "dark"], // Which base modes it provides - "preview": "preview.png" // Screenshot for settings UI + "variables": "theme.css", // CSS file with variable overrides + "presets": ["light", "dark"], // Which base modes it provides + "preview": "preview.png", // Screenshot for settings UI }, // ── Permissions (plugins only) ── "permissions": [ - "emails:read", // Read email data from store - "emails:write", // Modify email data (move, flag, etc.) + "emails:read", // Read email data from store + "emails:write", // Modify email data (move, flag, etc.) "contacts:read", "calendar:read", "settings:read", "settings:write", - "notifications", // Show toasts / browser notifications - "compose:toolbar", // Add buttons to composer toolbar - "sidebar:section", // Add sections to the sidebar - "viewer:action", // Add actions to email viewer toolbar - "navigation:tab", // Add a top-level navigation tab - "context-menu:email", // Extend email context menu - "keyboard-shortcuts", // Register keyboard shortcuts - "external-fetch" // Fetch external URLs (declared origins) + "notifications", // Show toasts / browser notifications + "compose:toolbar", // Add buttons to composer toolbar + "sidebar:section", // Add sections to the sidebar + "viewer:action", // Add actions to email viewer toolbar + "navigation:tab", // Add a top-level navigation tab + "context-menu:email", // Extend email context menu + "keyboard-shortcuts", // Register keyboard shortcuts + "external-fetch", // Fetch external URLs (declared origins) ], // ── External Origins (if external-fetch permission is declared) ── - "allowedOrigins": [ - "https://api.example.com" - ], + "allowedOrigins": ["https://api.example.com"], // ── Slots (declares which UI slots the plugin uses) ── - "slots": [ - "sidebar.bottom", - "compose.toolbar", - "viewer.actions" - ], + "slots": ["sidebar.bottom", "compose.toolbar", "viewer.actions"], // ── Settings Schema (plugin-specific preferences) ── "settings": { @@ -140,17 +134,17 @@ Every addon has an `addon.json` at its root: "type": "string", "label": "API Key", "description": "Your API key for the service.", - "secret": true + "secret": true, }, "enabled": { "type": "boolean", "label": "Enable integration", - "default": true - } + "default": true, + }, }, // ── i18n ── - "locales": "locales/" // Directory with {locale}.json files + "locales": "locales/", // Directory with {locale}.json files } ``` @@ -183,8 +177,8 @@ The app already uses CSS custom properties (variables) for all colors, defined i --font-family-base: "Inter", sans-serif; --font-family-mono: "JetBrains Mono", monospace; --radius-base: 8px; - --spacing-density: 1; /* 0.8 = compact, 1 = normal, 1.2 = comfortable */ - --shadow-elevation-1: 0 1px 3px rgba(0,0,0,0.08); + --spacing-density: 1; /* 0.8 = compact, 1 = normal, 1.2 = comfortable */ + --shadow-elevation-1: 0 1px 3px rgba(0, 0, 0, 0.08); } :root[data-theme="com.example.nord"].dark { @@ -209,16 +203,16 @@ User selects theme in Settings → Appearance ### 4.3 Theme Capabilities -| Capability | Mechanism | -|-----------|-----------| -| Colors | Override `--color-*` CSS variables | -| Typography | Override `--font-family-*` variables | -| Spacing/density | Override `--spacing-density` multiplier | -| Border radius | Override `--radius-*` variables | -| Shadows | Override `--shadow-*` variables | -| Dark mode variant | Provide `.dark` overrides | -| Tag/label colors | Override `--tag-color-*` palette | -| Custom CSS | Additional rules scoped under `[data-theme="..."]` | +| Capability | Mechanism | +| ----------------- | -------------------------------------------------- | +| Colors | Override `--color-*` CSS variables | +| Typography | Override `--font-family-*` variables | +| Spacing/density | Override `--spacing-density` multiplier | +| Border radius | Override `--radius-*` variables | +| Shadows | Override `--shadow-*` variables | +| Dark mode variant | Provide `.dark` overrides | +| Tag/label colors | Override `--tag-color-*` palette | +| Custom CSS | Additional rules scoped under `[data-theme="..."]` | ### 4.4 Theme Constraints @@ -271,7 +265,9 @@ export function activate(ctx: PluginContext) { ctx.contextMenu.register("email", { label: ctx.i18n.t("translateEmail"), icon: "Languages", - action: (emailId) => { /* ... */ }, + action: (emailId) => { + /* ... */ + }, }); } @@ -305,10 +301,10 @@ interface PluginContext { /** Store access (read-only or read-write based on permissions) */ stores: { - emails: PluginEmailStore; // If emails:read or emails:write - contacts: PluginContactStore; // If contacts:read - calendar: PluginCalendarStore; // If calendar:read - settings: PluginSettingsStore; // If settings:read or settings:write + emails: PluginEmailStore; // If emails:read or emails:write + contacts: PluginContactStore; // If contacts:read + calendar: PluginCalendarStore; // If calendar:read + settings: PluginSettingsStore; // If settings:read or settings:write }; /** Plugin-specific settings (defined in manifest "settings" schema) */ @@ -411,7 +407,8 @@ Next.js `import()` only resolves modules known at build time. To load plugins fr async function loadRemotePlugin(url: string): Promise { // 1. Fetch the plugin's JS bundle as text const response = await fetch(url); - if (!response.ok) throw new Error(`Failed to fetch plugin: ${response.status}`); + if (!response.ok) + throw new Error(`Failed to fetch plugin: ${response.status}`); const code = await response.text(); // 2. Validate size limit (500 KB default) @@ -437,11 +434,11 @@ async function loadRemotePlugin(url: string): Promise { // Shared dependencies exposed to plugins — avoids bundling duplicates const SHARED_DEPS: Record = { - "react": React, + react: React, "react/jsx-runtime": jsxRuntime, "lucide-react": lucideIcons, "date-fns": dateFns, - "sonner": sonner, + sonner: sonner, }; ``` @@ -474,16 +471,16 @@ interface AddonManagerState { interface InstalledAddon { manifest: AddonManifest; enabled: boolean; - installedAt: string; // ISO timestamp - source: AddonSource; // Where it was loaded from + installedAt: string; // ISO timestamp + source: AddonSource; // Where it was loaded from runtimeState: "inactive" | "active" | "error"; - error?: string; // Last activation error + error?: string; // Last activation error } type AddonSource = - | { type: "bundled" } // Shipped with the app - | { type: "url"; url: string } // Loaded from a URL - | { type: "local"; path: string }; // Development: local file + | { type: "bundled" } // Shipped with the app + | { type: "url"; url: string } // Loaded from a URL + | { type: "local"; path: string }; // Development: local file ``` --- @@ -530,20 +527,20 @@ function Slot({ name }: { name: string }) { #### Available Slots -| Slot Name | Location | Use Case | -|-----------|----------|----------| -| `sidebar.top` | Top of sidebar, below compose button | Quick-access widgets | -| `sidebar.bottom` | Bottom of sidebar, above storage quota | Extra navigation, widgets | -| `navigation.tabs` | Navigation rail, below contacts icon | New top-level views | -| `compose.toolbar` | Composer toolbar (formatting bar) | Encrypt, translate, AI assist buttons | -| `compose.footer` | Below composer body, above send button | Send-time options (delay, schedule) | -| `viewer.actions` | Email viewer toolbar | Custom actions (translate, summarize) | -| `viewer.header` | Above email body in viewer | Banners, warnings, metadata | -| `viewer.footer` | Below email body in viewer | Related content, suggestions | -| `list.toolbar` | Above email list | Additional filters, bulk actions | -| `settings.sections` | Settings page, below existing sections | Plugin settings panels | -| `calendar.toolbar` | Calendar view toolbar | Calendar-specific actions | -| `contacts.toolbar` | Contacts view toolbar | Contact-specific actions | +| Slot Name | Location | Use Case | +| ------------------- | -------------------------------------- | ------------------------------------- | +| `sidebar.top` | Top of sidebar, below compose button | Quick-access widgets | +| `sidebar.bottom` | Bottom of sidebar, above storage quota | Extra navigation, widgets | +| `navigation.tabs` | Navigation rail, below contacts icon | New top-level views | +| `compose.toolbar` | Composer toolbar (formatting bar) | Encrypt, translate, AI assist buttons | +| `compose.footer` | Below composer body, above send button | Send-time options (delay, schedule) | +| `viewer.actions` | Email viewer toolbar | Custom actions (translate, summarize) | +| `viewer.header` | Above email body in viewer | Banners, warnings, metadata | +| `viewer.footer` | Below email body in viewer | Related content, suggestions | +| `list.toolbar` | Above email list | Additional filters, bulk actions | +| `settings.sections` | Settings page, below existing sections | Plugin settings panels | +| `calendar.toolbar` | Calendar view toolbar | Calendar-specific actions | +| `contacts.toolbar` | Contacts view toolbar | Contact-specific actions | ### 7.2 Hook Events @@ -551,42 +548,42 @@ Plugins can listen to app events and state transitions: #### Email Hooks -| Event | Payload | Description | -|-------|---------|-------------| -| `email:selected` | `{ emailId, email }` | User selected an email | -| `email:opened` | `{ emailId, email }` | Email viewer rendered | -| `email:compose:open` | `{ mode, replyTo? }` | Composer opened | -| `email:compose:before-send` | `{ draft }` | Before sending — can modify draft | -| `email:compose:sent` | `{ emailId }` | Email sent successfully | -| `email:moved` | `{ emailId, from, to }` | Email moved between mailboxes | -| `email:deleted` | `{ emailId }` | Email deleted | -| `email:flagged` | `{ emailId, flags }` | Email flags changed | +| Event | Payload | Description | +| --------------------------- | ----------------------- | --------------------------------- | +| `email:selected` | `{ emailId, email }` | User selected an email | +| `email:opened` | `{ emailId, email }` | Email viewer rendered | +| `email:compose:open` | `{ mode, replyTo? }` | Composer opened | +| `email:compose:before-send` | `{ draft }` | Before sending — can modify draft | +| `email:compose:sent` | `{ emailId }` | Email sent successfully | +| `email:moved` | `{ emailId, from, to }` | Email moved between mailboxes | +| `email:deleted` | `{ emailId }` | Email deleted | +| `email:flagged` | `{ emailId, flags }` | Email flags changed | #### Calendar Hooks -| Event | Payload | Description | -|-------|---------|-------------| -| `calendar:event:created` | `{ event }` | New event created | -| `calendar:event:updated` | `{ event, changes }` | Event modified | -| `calendar:event:deleted` | `{ eventId }` | Event deleted | -| `calendar:view:changed` | `{ view, date }` | Calendar view switched | +| Event | Payload | Description | +| ------------------------ | -------------------- | ---------------------- | +| `calendar:event:created` | `{ event }` | New event created | +| `calendar:event:updated` | `{ event, changes }` | Event modified | +| `calendar:event:deleted` | `{ eventId }` | Event deleted | +| `calendar:view:changed` | `{ view, date }` | Calendar view switched | #### Contact Hooks -| Event | Payload | Description | -|-------|---------|-------------| -| `contact:selected` | `{ contactId }` | Contact selected | -| `contact:created` | `{ contact }` | New contact created | -| `contact:updated` | `{ contact }` | Contact modified | +| Event | Payload | Description | +| ------------------ | --------------- | ------------------- | +| `contact:selected` | `{ contactId }` | Contact selected | +| `contact:created` | `{ contact }` | New contact created | +| `contact:updated` | `{ contact }` | Contact modified | #### App Hooks -| Event | Payload | Description | -|-------|---------|-------------| -| `app:ready` | `{}` | App fully loaded | -| `app:theme:changed` | `{ theme }` | Theme switched | -| `app:locale:changed` | `{ locale }` | Language changed | -| `app:navigation` | `{ from, to }` | User navigated between views | +| Event | Payload | Description | +| -------------------- | -------------- | ---------------------------- | +| `app:ready` | `{}` | App fully loaded | +| `app:theme:changed` | `{ theme }` | Theme switched | +| `app:locale:changed` | `{ locale }` | Language changed | +| `app:navigation` | `{ from, to }` | User navigated between views | --- @@ -609,18 +606,19 @@ Permissions are enforced at the `PluginContext` level — if a plugin didn't dec ### 8.2 Sandboxing Strategy -| Layer | Mechanism | -|-------|-----------| -| **Store access** | `PluginContext` exposes only permitted store slices. Write access returns proxied objects — mutations are validated before applying. | -| **DOM access** | Plugin components render inside an ``. They receive a scoped React tree — no direct `document` manipulation. | -| **Network** | `ctx.fetch()` is a controlled wrapper. Requests are only allowed to origins listed in `allowedOrigins`. All other `fetch` / `XMLHttpRequest` calls from plugin code are blocked via CSP headers. | -| **Storage** | Plugins use `ctx.config` (backed by a namespaced key in `localStorage`). No direct `localStorage` / `sessionStorage` access. | -| **Error isolation** | Each plugin slot is wrapped in an `AddonErrorBoundary`. A crashing plugin is caught and disabled without affecting the rest of the app. | -| **Resource limits** | Plugin CSS is limited to 100 KB. Plugin JS bundles are limited to 500 KB (configurable). | +| Layer | Mechanism | +| ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| **Store access** | `PluginContext` exposes only permitted store slices. Write access returns proxied objects — mutations are validated before applying. | +| **DOM access** | Plugin components render inside an ``. They receive a scoped React tree — no direct `document` manipulation. | +| **Network** | `ctx.fetch()` is a controlled wrapper. Requests are only allowed to origins listed in `allowedOrigins`. All other `fetch` / `XMLHttpRequest` calls from plugin code are blocked via CSP headers. | +| **Storage** | Plugins use `ctx.config` (backed by a namespaced key in `localStorage`). No direct `localStorage` / `sessionStorage` access. | +| **Error isolation** | Each plugin slot is wrapped in an `AddonErrorBoundary`. A crashing plugin is caught and disabled without affecting the rest of the app. | +| **Resource limits** | Plugin CSS is limited to 100 KB. Plugin JS bundles are limited to 500 KB (configurable). | ### 8.3 Content Security Policy Theme CSS is sanitized to disallow: + - `url()` references to external domains (only data URIs and same-origin). - `@import` statements. - `expression()` or `behavior:` (legacy IE attack vectors). @@ -650,11 +648,11 @@ function AddonErrorBoundary({ addonId, children }) { ### 9.1 Addon Formats -| Format | Description | Use Case | -|--------|-------------|----------| -| **Bundled** | Shipped inside the app's `/addons/` directory | Default themes, first-party plugins | -| **URL** | Loaded from a remote URL at runtime | Self-hosted or third-party addons | -| **Local file** | Loaded from a local path (dev mode only) | Plugin development | +| Format | Description | Use Case | +| -------------- | --------------------------------------------- | ----------------------------------- | +| **Bundled** | Shipped inside the app's `/addons/` directory | Default themes, first-party plugins | +| **URL** | Loaded from a remote URL at runtime | Self-hosted or third-party addons | +| **Local file** | Loaded from a local path (dev mode only) | Plugin development | ### 9.2 Addon Bundle Structure @@ -692,12 +690,14 @@ addons/ ### 9.4 Installation Flow **From URL:** + 1. User pastes addon URL into Settings → Addons → "Install from URL". 2. App fetches `{url}/addon.json`, validates schema and compatibility. 3. Manifest is stored in addon registry (`localStorage`). 4. On next activation, the addon's assets are fetched and cached. **Bundled:** + 1. Addons in `/addons/` are auto-discovered at build time. 2. A generated `addon-registry.json` maps addon IDs to their local paths. 3. Bundled addons appear pre-installed (but can be disabled). @@ -779,6 +779,7 @@ Appearance ``` When a theme is selected, the app: + 1. Sets `data-theme` attribute on ``. 2. Loads the theme's CSS file. 3. Persists the choice in `ThemeStore`. @@ -894,6 +895,7 @@ nord-theme/ ``` `addon.json`: + ```json { "id": "org.nordtheme.bulwark-webmail", @@ -925,6 +927,7 @@ email-translator/ ``` `addon.json`: + ```json { "id": "com.example.email-translator",