docs: update contributing guidelines and enhance README formatting

This commit is contained in:
Linus Rath
2026-03-13 14:12:20 +01:00
parent 82a790de0f
commit 02808fa4c3
5 changed files with 193 additions and 151 deletions
+9 -2
View File
@@ -7,17 +7,20 @@ Thank you for your interest in contributing to Bulwark Webmail! This document pr
### Development Setup ### Development Setup
1. **Fork and clone** the repository: 1. **Fork and clone** the repository:
```bash ```bash
git clone https://github.com/bulwarkmail/webmail.git git clone https://github.com/bulwarkmail/webmail.git
cd webmail cd webmail
``` ```
2. **Install dependencies**: 2. **Install dependencies**:
```bash ```bash
npm install npm install
``` ```
3. **Set up environment**: 3. **Set up environment**:
```bash ```bash
cp .env.example .env.local cp .env.example .env.local
# Edit .env.local with your JMAP server URL # 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 ### Key Rules
1. **Never hardcode user-facing text** - Always use translations: 1. **Never hardcode user-facing text** - Always use translations:
```tsx ```tsx
const t = useTranslations('namespace'); const t = useTranslations("namespace");
return <div>{t('key')}</div>; return <div>{t("key")}</div>;
``` ```
2. **Translation file locations**: 2. **Translation file locations**:
@@ -109,6 +113,7 @@ This project uses **next-intl** for internationalization. Please follow these gu
### Before Submitting ### Before Submitting
1. **Create a feature branch**: 1. **Create a feature branch**:
```bash ```bash
git checkout -b feature/your-feature-name git checkout -b feature/your-feature-name
``` ```
@@ -147,6 +152,7 @@ Follow the conventional commits format:
- `chore:` - Maintenance tasks - `chore:` - Maintenance tasks
Examples: Examples:
``` ```
feat: add email threading support feat: add email threading support
fix: resolve attachment download issue fix: resolve attachment download issue
@@ -184,6 +190,7 @@ webmail/
## Questions? ## Questions?
If you have questions about contributing, feel free to: If you have questions about contributing, feel free to:
- Open an issue for discussion - Open an issue for discussion
- Check existing issues and pull requests - Check existing issues and pull requests
+4 -2
View File
@@ -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`. Endpoints are auto-discovered via `.well-known/oauth-authorization-server` or `.well-known/openid-configuration`.
</details> </details>
<details> <details>
@@ -210,12 +211,13 @@ 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). Credentials encrypted with AES-256-GCM, stored in an httpOnly cookie (30-day expiry).
</details> </details>
## Keyboard Shortcuts ## Keyboard Shortcuts
| Key | Action | | Key | Action |
|-----|--------| | ------------- | ----------------------- |
| `j` / `k` | Navigate between emails | | `j` / `k` | Navigate between emails |
| `Enter` / `o` | Open email | | `Enter` / `o` | Open email |
| `Esc` | Close / deselect | | `Esc` | Close / deselect |
@@ -231,7 +233,7 @@ Credentials encrypted with AES-256-GCM, stored in an httpOnly cookie (30-day exp
## Tech Stack ## Tech Stack
| | | | | |
|---|---| | ------------- | ------------------------------------------------- |
| **Framework** | [Next.js 16](https://nextjs.org/) with App Router | | **Framework** | [Next.js 16](https://nextjs.org/) with App Router |
| **Language** | TypeScript | | **Language** | TypeScript |
| **Styling** | [Tailwind CSS v4](https://tailwindcss.com/) | | **Styling** | [Tailwind CSS v4](https://tailwindcss.com/) |
+22
View File
@@ -5,12 +5,14 @@ This document tracks the development status and planned features for Bulwark Web
## Completed Features ## Completed Features
### Core Infrastructure ### Core Infrastructure
- [x] Next.js 16 with TypeScript and App Router - [x] Next.js 16 with TypeScript and App Router
- [x] Tailwind CSS v4 with Oxide engine - [x] Tailwind CSS v4 with Oxide engine
- [x] Zustand state management - [x] Zustand state management
- [x] Custom JMAP client implementation (RFC 8620) - [x] Custom JMAP client implementation (RFC 8620)
### Authentication ### Authentication
- [x] Login with JMAP server authentication - [x] Login with JMAP server authentication
- [x] Session management (no password storage for security) - [x] Session management (no password storage for security)
- [x] Username autocomplete with history - [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) - [x] "Remember me" session persistence for Basic Auth (AES-256-GCM encrypted httpOnly cookie)
### JMAP Server Connection ### JMAP Server Connection
- [x] Session establishment and keep-alive - [x] Session establishment and keep-alive
- [x] Connection error handling and retries - [x] Connection error handling and retries
- [x] CORS error detection with actionable user guidance - [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) - [x] Shared folders support (multi-account access)
### Email Operations ### Email Operations
- [x] Email fetching and display - [x] Email fetching and display
- [x] Full HTML email rendering - [x] Full HTML email rendering
- [x] Compose, reply, reply-all, forward - [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) - [x] Email threading (Gmail-style inline expansion)
### Real-time Updates ### Real-time Updates
- [x] EventSource for JMAP push notifications - [x] EventSource for JMAP push notifications
- [x] State synchronization - [x] State synchronization
- [x] Email arrival notifications - [x] Email arrival notifications
@@ -55,6 +60,7 @@ This document tracks the development status and planned features for Bulwark Web
- [x] Mailbox change handling - [x] Mailbox change handling
### User Interface ### User Interface
- [x] Three-pane layout (sidebar, list, viewer) - [x] Three-pane layout (sidebar, list, viewer)
- [x] Minimalist design system - [x] Minimalist design system
- [x] Dark and light theme support - [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) - [x] Screen reader live region announcements (sr-only)
### Internationalization ### Internationalization
- [x] English language support - [x] English language support
- [x] French language support - [x] French language support
- [x] Japanese 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 - [x] Language preference persistence
### Security & Accessibility ### Security & Accessibility
- [x] External content blocked by default - [x] External content blocked by default
- [x] HTML sanitization with DOMPurify - [x] HTML sanitization with DOMPurify
- [x] User control for loading external content - [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 - [x] Screen reader sr-only live region for dynamic announcements
### Identity Management ### Identity Management
- [x] Multiple sender identities (name, email, signature) - [x] Multiple sender identities (name, email, signature)
- [x] Sub-addressing support (user+tag@domain.com) - [x] Sub-addressing support (user+tag@domain.com)
- [x] Per-identity signatures - [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 - [x] Primary identity (matching login) selected by default in composer
### Address Book & Contacts ### Address Book & Contacts
- [x] Contact store with JMAP sync and local fallback - [x] Contact store with JMAP sync and local fallback
- [x] Contact CRUD operations (create, read, update, delete) - [x] Contact CRUD operations (create, read, update, delete)
- [x] Contacts list view with search/filter - [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) - [x] i18n support for contacts (all 8 languages)
### Vacation Responder ### Vacation Responder
- [x] JMAP VacationResponse singleton management - [x] JMAP VacationResponse singleton management
- [x] Settings tab with date range and message configuration - [x] Settings tab with date range and message configuration
- [x] Sidebar indicator when vacation auto-reply is active - [x] Sidebar indicator when vacation auto-reply is active
- [x] i18n support (all 8 languages) - [x] i18n support (all 8 languages)
### Calendar Integration ### Calendar Integration
- [x] JMAP Calendar types (RFC 8984) and client methods - [x] JMAP Calendar types (RFC 8984) and client methods
- [x] Calendar capability detection (urn:ietf:params:jmap:calendars) - [x] Calendar capability detection (urn:ietf:params:jmap:calendars)
- [x] Calendar store with Zustand (persist middleware) - [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) - [x] Event duplication button in modal (clones event +1 day, opens for editing)
### Email Filters ### Email Filters
- [x] JMAP Sieve Scripts (RFC 9661) with capability detection - [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] 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 - [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) - [x] i18n support (all 8 languages)
### Email Templates ### Email Templates
- [x] Reusable email templates with local storage persistence - [x] Reusable email templates with local storage persistence
- [x] Category organization (General, Business, Personal, Support, Follow-up, custom) - [x] Category organization (General, Business, Personal, Support, Follow-up, custom)
- [x] Dynamic placeholder variables with auto-fill from composer context - [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) - [x] i18n support (all 8 languages)
### Email Display ### Email Display
- [x] Proper email layout without horizontal scroll or clipping - [x] Proper email layout without horizontal scroll or clipping
- [x] Blocked image container collapsing (no empty spaces in newsletters) - [x] Blocked image container collapsing (no empty spaces in newsletters)
### Testing ### Testing
- [x] Unit tests for validation utilities (57 tests) - [x] Unit tests for validation utilities (57 tests)
- [x] Unit tests for email sanitization (27 tests) - [x] Unit tests for email sanitization (27 tests)
- [x] Unit tests for color transformation (40 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 - [x] Playwright E2E framework setup
### Deployment ### Deployment
- [x] Runtime environment variables (Docker-friendly configuration) - [x] Runtime environment variables (Docker-friendly configuration)
- [x] Health check endpoint - [x] Health check endpoint
- [x] Docker support (multi-stage build, docker-compose, standalone output) - [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 ## Planned Features
### Advanced Features ### Advanced Features
- [ ] Free/busy queries (Principal/getAvailability) - [ ] Free/busy queries (Principal/getAvailability)
- [ ] Calendar sharing UI (JMAP Sharing RFC 9670) - [ ] Calendar sharing UI (JMAP Sharing RFC 9670)
- [ ] Email encryption (PGP/GPG) - [ ] Email encryption (PGP/GPG)
### Performance Optimizations ### Performance Optimizations
- [ ] Email content caching - [ ] Email content caching
- [ ] Bundle size optimization - [ ] Bundle size optimization
- [ ] Service worker for offline support - [ ] Service worker for offline support
- [ ] Lazy loading for attachments - [ ] Lazy loading for attachments
### Testing (Remaining) ### Testing (Remaining)
- [ ] E2E tests with real JMAP server - [ ] E2E tests with real JMAP server
- [ ] Accessibility testing - [ ] Accessibility testing
- [ ] Performance testing - [ ] Performance testing
### Deployment ### Deployment
- [ ] Production build optimizations - [ ] Production build optimizations
- [ ] Monitoring and logging - [ ] Monitoring and logging
### Security Enhancements ### Security Enhancements
- [ ] Rate limiting - [ ] Rate limiting
## Known Issues ## Known Issues
+9 -1
View File
@@ -6,7 +6,15 @@ services:
env_file: env_file:
- .env.local - .env.local
healthcheck: 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 interval: 30s
timeout: 5s timeout: 5s
retries: 3 retries: 3
+34 -31
View File
@@ -37,7 +37,7 @@ This document describes a system that allows the Bulwark Webmail application to
### Rebuild Requirements ### Rebuild Requirements
| Addon Type | Source | Rebuild Needed? | | Addon Type | Source | Rebuild Needed? |
|-----------|--------|----------------| | ---------- | ---------------------------- | ------------------------------------------- |
| Theme | Bundled (`/addons/themes/`) | **Yes** — included at build time | | Theme | Bundled (`/addons/themes/`) | **Yes** — included at build time |
| Theme | URL (remote) | **No** — CSS loaded via `<link>` at runtime | | Theme | URL (remote) | **No** — CSS loaded via `<link>` at runtime |
| Plugin | Bundled (`/addons/plugins/`) | **Yes** — included at build time | | Plugin | Bundled (`/addons/plugins/`) | **Yes** — included at build time |
@@ -57,7 +57,7 @@ Bundled addons are compiled into the app's static assets during `next build`. Ad
## 2. Terminology ## 2. Terminology
| Term | Definition | | Term | Definition |
|------|-----------| | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
| **Addon** | Any installable extension — umbrella term for themes and plugins. | | **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. | | **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. | | **Plugin** | An addon that adds or modifies functionality. Ships as a JS/TS module + a manifest. May include a theme. |
@@ -80,14 +80,14 @@ Every addon has an `addon.json` at its root:
"description": "A brief description.", "description": "A brief description.",
"author": { "author": {
"name": "Jane Doe", "name": "Jane Doe",
"url": "https://example.com" "url": "https://example.com",
}, },
"license": "MIT", "license": "MIT",
"homepage": "https://example.com/my-addon", "homepage": "https://example.com/my-addon",
// ── Compatibility ── // ── Compatibility ──
"engine": { "engine": {
"webmail": ">=1.0.0" // Required host app version range "webmail": ">=1.0.0", // Required host app version range
}, },
// ── Type ── // ── Type ──
@@ -101,7 +101,7 @@ Every addon has an `addon.json` at its root:
"theme": { "theme": {
"variables": "theme.css", // CSS file with variable overrides "variables": "theme.css", // CSS file with variable overrides
"presets": ["light", "dark"], // Which base modes it provides "presets": ["light", "dark"], // Which base modes it provides
"preview": "preview.png" // Screenshot for settings UI "preview": "preview.png", // Screenshot for settings UI
}, },
// ── Permissions (plugins only) ── // ── Permissions (plugins only) ──
@@ -119,20 +119,14 @@ Every addon has an `addon.json` at its root:
"navigation:tab", // Add a top-level navigation tab "navigation:tab", // Add a top-level navigation tab
"context-menu:email", // Extend email context menu "context-menu:email", // Extend email context menu
"keyboard-shortcuts", // Register keyboard shortcuts "keyboard-shortcuts", // Register keyboard shortcuts
"external-fetch" // Fetch external URLs (declared origins) "external-fetch", // Fetch external URLs (declared origins)
], ],
// ── External Origins (if external-fetch permission is declared) ── // ── External Origins (if external-fetch permission is declared) ──
"allowedOrigins": [ "allowedOrigins": ["https://api.example.com"],
"https://api.example.com"
],
// ── Slots (declares which UI slots the plugin uses) ── // ── Slots (declares which UI slots the plugin uses) ──
"slots": [ "slots": ["sidebar.bottom", "compose.toolbar", "viewer.actions"],
"sidebar.bottom",
"compose.toolbar",
"viewer.actions"
],
// ── Settings Schema (plugin-specific preferences) ── // ── Settings Schema (plugin-specific preferences) ──
"settings": { "settings": {
@@ -140,17 +134,17 @@ Every addon has an `addon.json` at its root:
"type": "string", "type": "string",
"label": "API Key", "label": "API Key",
"description": "Your API key for the service.", "description": "Your API key for the service.",
"secret": true "secret": true,
}, },
"enabled": { "enabled": {
"type": "boolean", "type": "boolean",
"label": "Enable integration", "label": "Enable integration",
"default": true "default": true,
} },
}, },
// ── i18n ── // ── i18n ──
"locales": "locales/" // Directory with {locale}.json files "locales": "locales/", // Directory with {locale}.json files
} }
``` ```
@@ -184,7 +178,7 @@ The app already uses CSS custom properties (variables) for all colors, defined i
--font-family-mono: "JetBrains Mono", monospace; --font-family-mono: "JetBrains Mono", monospace;
--radius-base: 8px; --radius-base: 8px;
--spacing-density: 1; /* 0.8 = compact, 1 = normal, 1.2 = comfortable */ --spacing-density: 1; /* 0.8 = compact, 1 = normal, 1.2 = comfortable */
--shadow-elevation-1: 0 1px 3px rgba(0,0,0,0.08); --shadow-elevation-1: 0 1px 3px rgba(0, 0, 0, 0.08);
} }
:root[data-theme="com.example.nord"].dark { :root[data-theme="com.example.nord"].dark {
@@ -210,7 +204,7 @@ User selects theme in Settings → Appearance
### 4.3 Theme Capabilities ### 4.3 Theme Capabilities
| Capability | Mechanism | | Capability | Mechanism |
|-----------|-----------| | ----------------- | -------------------------------------------------- |
| Colors | Override `--color-*` CSS variables | | Colors | Override `--color-*` CSS variables |
| Typography | Override `--font-family-*` variables | | Typography | Override `--font-family-*` variables |
| Spacing/density | Override `--spacing-density` multiplier | | Spacing/density | Override `--spacing-density` multiplier |
@@ -271,7 +265,9 @@ export function activate(ctx: PluginContext) {
ctx.contextMenu.register("email", { ctx.contextMenu.register("email", {
label: ctx.i18n.t("translateEmail"), label: ctx.i18n.t("translateEmail"),
icon: "Languages", icon: "Languages",
action: (emailId) => { /* ... */ }, action: (emailId) => {
/* ... */
},
}); });
} }
@@ -411,7 +407,8 @@ Next.js `import()` only resolves modules known at build time. To load plugins fr
async function loadRemotePlugin(url: string): Promise<PluginModule> { async function loadRemotePlugin(url: string): Promise<PluginModule> {
// 1. Fetch the plugin's JS bundle as text // 1. Fetch the plugin's JS bundle as text
const response = await fetch(url); 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(); const code = await response.text();
// 2. Validate size limit (500 KB default) // 2. Validate size limit (500 KB default)
@@ -437,11 +434,11 @@ async function loadRemotePlugin(url: string): Promise<PluginModule> {
// Shared dependencies exposed to plugins — avoids bundling duplicates // Shared dependencies exposed to plugins — avoids bundling duplicates
const SHARED_DEPS: Record<string, unknown> = { const SHARED_DEPS: Record<string, unknown> = {
"react": React, react: React,
"react/jsx-runtime": jsxRuntime, "react/jsx-runtime": jsxRuntime,
"lucide-react": lucideIcons, "lucide-react": lucideIcons,
"date-fns": dateFns, "date-fns": dateFns,
"sonner": sonner, sonner: sonner,
}; };
``` ```
@@ -531,7 +528,7 @@ function Slot({ name }: { name: string }) {
#### Available Slots #### Available Slots
| Slot Name | Location | Use Case | | Slot Name | Location | Use Case |
|-----------|----------|----------| | ------------------- | -------------------------------------- | ------------------------------------- |
| `sidebar.top` | Top of sidebar, below compose button | Quick-access widgets | | `sidebar.top` | Top of sidebar, below compose button | Quick-access widgets |
| `sidebar.bottom` | Bottom of sidebar, above storage quota | Extra navigation, widgets | | `sidebar.bottom` | Bottom of sidebar, above storage quota | Extra navigation, widgets |
| `navigation.tabs` | Navigation rail, below contacts icon | New top-level views | | `navigation.tabs` | Navigation rail, below contacts icon | New top-level views |
@@ -552,7 +549,7 @@ Plugins can listen to app events and state transitions:
#### Email Hooks #### Email Hooks
| Event | Payload | Description | | Event | Payload | Description |
|-------|---------|-------------| | --------------------------- | ----------------------- | --------------------------------- |
| `email:selected` | `{ emailId, email }` | User selected an email | | `email:selected` | `{ emailId, email }` | User selected an email |
| `email:opened` | `{ emailId, email }` | Email viewer rendered | | `email:opened` | `{ emailId, email }` | Email viewer rendered |
| `email:compose:open` | `{ mode, replyTo? }` | Composer opened | | `email:compose:open` | `{ mode, replyTo? }` | Composer opened |
@@ -565,7 +562,7 @@ Plugins can listen to app events and state transitions:
#### Calendar Hooks #### Calendar Hooks
| Event | Payload | Description | | Event | Payload | Description |
|-------|---------|-------------| | ------------------------ | -------------------- | ---------------------- |
| `calendar:event:created` | `{ event }` | New event created | | `calendar:event:created` | `{ event }` | New event created |
| `calendar:event:updated` | `{ event, changes }` | Event modified | | `calendar:event:updated` | `{ event, changes }` | Event modified |
| `calendar:event:deleted` | `{ eventId }` | Event deleted | | `calendar:event:deleted` | `{ eventId }` | Event deleted |
@@ -574,7 +571,7 @@ Plugins can listen to app events and state transitions:
#### Contact Hooks #### Contact Hooks
| Event | Payload | Description | | Event | Payload | Description |
|-------|---------|-------------| | ------------------ | --------------- | ------------------- |
| `contact:selected` | `{ contactId }` | Contact selected | | `contact:selected` | `{ contactId }` | Contact selected |
| `contact:created` | `{ contact }` | New contact created | | `contact:created` | `{ contact }` | New contact created |
| `contact:updated` | `{ contact }` | Contact modified | | `contact:updated` | `{ contact }` | Contact modified |
@@ -582,7 +579,7 @@ Plugins can listen to app events and state transitions:
#### App Hooks #### App Hooks
| Event | Payload | Description | | Event | Payload | Description |
|-------|---------|-------------| | -------------------- | -------------- | ---------------------------- |
| `app:ready` | `{}` | App fully loaded | | `app:ready` | `{}` | App fully loaded |
| `app:theme:changed` | `{ theme }` | Theme switched | | `app:theme:changed` | `{ theme }` | Theme switched |
| `app:locale:changed` | `{ locale }` | Language changed | | `app:locale:changed` | `{ locale }` | Language changed |
@@ -610,7 +607,7 @@ Permissions are enforced at the `PluginContext` level — if a plugin didn't dec
### 8.2 Sandboxing Strategy ### 8.2 Sandboxing Strategy
| Layer | Mechanism | | Layer | Mechanism |
|-------|-----------| | ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| **Store access** | `PluginContext` exposes only permitted store slices. Write access returns proxied objects — mutations are validated before applying. | | **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 `<AddonErrorBoundary>`. They receive a scoped React tree — no direct `document` manipulation. | | **DOM access** | Plugin components render inside an `<AddonErrorBoundary>`. 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. | | **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. |
@@ -621,6 +618,7 @@ Permissions are enforced at the `PluginContext` level — if a plugin didn't dec
### 8.3 Content Security Policy ### 8.3 Content Security Policy
Theme CSS is sanitized to disallow: Theme CSS is sanitized to disallow:
- `url()` references to external domains (only data URIs and same-origin). - `url()` references to external domains (only data URIs and same-origin).
- `@import` statements. - `@import` statements.
- `expression()` or `behavior:` (legacy IE attack vectors). - `expression()` or `behavior:` (legacy IE attack vectors).
@@ -651,7 +649,7 @@ function AddonErrorBoundary({ addonId, children }) {
### 9.1 Addon Formats ### 9.1 Addon Formats
| Format | Description | Use Case | | Format | Description | Use Case |
|--------|-------------|----------| | -------------- | --------------------------------------------- | ----------------------------------- |
| **Bundled** | Shipped inside the app's `/addons/` directory | Default themes, first-party plugins | | **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 | | **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 | | **Local file** | Loaded from a local path (dev mode only) | Plugin development |
@@ -692,12 +690,14 @@ addons/
### 9.4 Installation Flow ### 9.4 Installation Flow
**From URL:** **From URL:**
1. User pastes addon URL into Settings → Addons → "Install from URL". 1. User pastes addon URL into Settings → Addons → "Install from URL".
2. App fetches `{url}/addon.json`, validates schema and compatibility. 2. App fetches `{url}/addon.json`, validates schema and compatibility.
3. Manifest is stored in addon registry (`localStorage`). 3. Manifest is stored in addon registry (`localStorage`).
4. On next activation, the addon's assets are fetched and cached. 4. On next activation, the addon's assets are fetched and cached.
**Bundled:** **Bundled:**
1. Addons in `/addons/` are auto-discovered at build time. 1. Addons in `/addons/` are auto-discovered at build time.
2. A generated `addon-registry.json` maps addon IDs to their local paths. 2. A generated `addon-registry.json` maps addon IDs to their local paths.
3. Bundled addons appear pre-installed (but can be disabled). 3. Bundled addons appear pre-installed (but can be disabled).
@@ -779,6 +779,7 @@ Appearance
``` ```
When a theme is selected, the app: When a theme is selected, the app:
1. Sets `data-theme` attribute on `<html>`. 1. Sets `data-theme` attribute on `<html>`.
2. Loads the theme's CSS file. 2. Loads the theme's CSS file.
3. Persists the choice in `ThemeStore`. 3. Persists the choice in `ThemeStore`.
@@ -894,6 +895,7 @@ nord-theme/
``` ```
`addon.json`: `addon.json`:
```json ```json
{ {
"id": "org.nordtheme.bulwark-webmail", "id": "org.nordtheme.bulwark-webmail",
@@ -925,6 +927,7 @@ email-translator/
``` ```
`addon.json`: `addon.json`:
```json ```json
{ {
"id": "com.example.email-translator", "id": "com.example.email-translator",