CRITICAL fixes: - C1: Error swallowing - throw TransportError on network failure in getEmails/searchEmails - C2: Recurrence expansion ID delimiter changed from ':' to '::occurrence::' - C3: Cross-account calendar event UID dedup after multi-account aggregation - C4: Admin session token revocation via JTI blacklist on logout - C6: FTS5 schema-drop - add warning log for automatic reindex trigger - C7: Settings lock - gate updateSetting() with isSettingLocked() check - C8: Offline push pause - add offline event handler that closes push transports HIGH fixes: - H1: Push handler - add ContactCard and FileNode branches - H2: WS fallback - await state snapshot before reconcileAfterWebSocketFallback - H3: Auth rate limiting - add checkUserAuthRateLimit to session and token routes - H4: OAuth logs - strip access_token from error log context - H7: Template XSS - apply DOMPurify to HTML template body on import - H8: Secure cookie - derive from x-forwarded-proto, not NODE_ENV - H9: bcrypt fix - remove bcrypt prefixes from isHashed() so scrypt-only - H13: calendarTasksEnabled - apply admin gate at runtime in calendar page - H14: Task mutations - add try/catch error handling to update/delete/toggle - H18: autoSelectReplyIdentity default changed from false to true Deferred: P1.3 (C5 auth localStorage encryption) - requires custom Zustand persist adapter.
16 KiB
16 KiB
Wave 0 — Feature Inventory
Run: 2026-08-07-v1.7.8-baseline
Skill: VNCmailgraph (adapted for Next.js/Zustand)
Commit: d8bebb531f
Version: vnc-v0.3.0-94-gd8bebb53 (VERSION=1.7.8)
Codebase: ~188K LOC across 745 TS/TSX files (excluding node_modules, .git)
Framework: Next.js 16 (App Router) + React 19 + Zustand 5
Backend: Stalwart Mail Server (JMAP protocol)
Targets: Web (PWA), Electron Desktop, Native (planned via Capacitor/RN)
Method Adaptation
The original VNCmailgraph methodology assumes Angular + NgRx + isCordova/isElectron gates. This codebase uses:
- Next.js 16 App Router (not Angular) — routes defined via file-system routing
- Zustand (not NgRx) — stores in
stores/*.ts, accessed via hooks isElectronShell()(notisCordova/isElectron) — platform detection viawindow.vnc- No
zimbra-features.ts— feature flags are inlib/admin/types.ts(FeatureGates,SettingsPolicy) - JMAP protocol (not Zimbra SOAP) —
lib/jmap/client.ts(7446 lines)
The audit method translates cleanly: feature domains = page routes + component trees; shared substrate = stores + lib services; coupling = cross-store imports.
Feature Domains (derived from code)
F1 — Mail / Email (CORE)
- Routes:
/app/(main)/[locale]/page.tsx(3758 lines — main mail client) - Components:
components/email/(24 files): email-list, email-viewer (5102 lines), email-composer (3432 lines), thread-conversation-view, rich-text-editor (TipTap), tag-picker, email-hover-actions, recipient-popover, calendar-invitation-banner, unsubscribe-banner, read-receipt-banner, message-list-tabs - Store:
stores/email-store.ts(4087 lines) — largest store, manages emails, mailboxes, threads, search, tags, push connection, cross-account views - Protocol:
lib/jmap/client.ts(7446 lines) — the JMAP client powering all mail operations - Sub-features: Compose/drafts, threading, unified mailbox, cross-account views, search, tags/keywords, attachments, scheduled send, read receipts (MDN), archive modes, virtual scrolling, TNEF extraction, .eml import, print
F2 — Calendar
- Routes:
/app/(main)/[locale]/calendar/page.tsx - Components:
components/calendar/(22 files): month/week/day/agenda views, event-modal (1375 lines), mini-calendar, task-modal, recurrence-editor, ical-import/subscription modals, participant-input - Store:
stores/calendar-store.ts(1289 lines) — calendars, events, multi-account aggregation, recurrence expansion, iMIP, CalDAV - Sub-features: Month/week/day/agenda views, drag-to-reschedule, recurring events, iMIP invitations, .ics import, birthday calendar, virtual locations, tasks, CalDAV shared calendars
F3 — Contacts
- Routes:
/app/(main)/[locale]/contacts/page.tsx - Components:
components/contacts/(17 files): contact-list, contact-detail, contact-form, import-dialog, groups - Store:
stores/contact-store.ts(1146 lines) — contact cards, address books, multi-account, vCard import/export - Sub-features: JMAP sync (RFC 9553/9610), address books, groups, vCard import/export, trusted senders, autocomplete
F4 — Files / Briefcase
- Routes:
/app/(main)/[locale]/files/page.tsx - Components:
components/files/(10 files): file-browser (2022 lines), folder-tree, upload-area, preview-modal - Stores:
stores/file-store.ts(1050 lines) +stores/webdav-store.ts(480 lines) — dual storage backends - Sub-features: JMAP FileNode browsing, WebDAV upload with progress, grid/list views, preview, cut/copy/paste, favorites, sharing (RFC 9670)
F5 — Tasks
- Store:
stores/task-store.ts(95 lines) — VTODO CRUD, filter by pending/completed/overdue - Components:
components/calendar/task-modal.tsx,task-list-view.tsx,task-toolbar.tsx - Feature gate:
calendarTasksEnabledin admin policy
F6 — Settings / Preferences
- Routes:
/app/(main)/[locale]/settings/page.tsx - Components:
components/settings/(33 files) — account, appearance, themes, layout, reading, composing, notification, folder, filter, vacation, identity, template, keyword, calendar, contacts, files, downloads, sidebar-apps, plugins, language, protocol-handler, AI, debug, about - Store:
stores/settings-store.ts(1221 lines) — all user settings with cross-device encrypted sync - Sub-features: Theme/density/font, layout preferences, reply/signature behavior, notification preferences, mailto:/webcal: protocol handling, settings sync (AES-256-GCM)
F7 — Filters / Sieve
- Components:
components/filters/(2 files): filter-rule-modal (534 lines), sieve-editor-modal - Store:
stores/filter-store.ts(254 lines) — Sieve script management - Lib:
lib/sieve/(parser + generator) — round-trip for external scripts - Sub-features: Visual rule builder, raw Sieve editor, vacation responder, opaque script preservation
F8 — Templates
- Components:
components/templates/(4 files): template-picker, template-form, manager-modal, placeholder-fill - Store:
stores/template-store.ts(153 lines) — CRUD, favorites, import/export - Feature gate:
templatesEnabled
F9 — Identity / Aliases
- Components:
components/identity/(3 files): identity-manager-modal (407 lines), identity-form, sub-address-helper - Store:
stores/identity-store.ts(140 lines) — sender identities, sub-addressing - Sub-features: Multiple sender identities per account, sub-addressing (
user+tag@domain)
F10 — AI Assistant
- Components:
components/ai/ai-ask-button.tsx(264 lines) - Lib:
lib/ai/(9 files + retrieval/): local-client, local-discovery, opencode, entitlement, key-store, retrieval/fusion - API:
app/api/ai/(server/chat, server/models, retrieve, opencode/chat, opencode/models, policy) - Sub-features: Local LLM (Ollama), server-hosted AI, BYOK, OpenCode runtime, mail retrieval with RRF fusion, FTS5 search index
- Feature gate:
aiAssistantEnabled(default=true)
F11 — Admin / Management
- Routes: 14 admin pages (dashboard, settings, branding, auth, password, policy, plugins, themes, marketplace, version, telemetry, logs)
- Lib:
lib/admin/(18 files): config-manager, session, password, audit, plugin-registry, plugin-approvals, plugin-signing, bundled-plugins, domain-branding, CSP - Store:
stores/admin-tab-store.ts(42 lines)
F12 — Plugin System
- Components:
components/plugins/(7 files): plugin-slot, iframe-slot, dialog-host, consent-dialog, error-boundary - Store:
stores/plugin-store.ts(596 lines) — lifecycle management - Lib:
lib/plugin-sandbox/(13 files): runtime, host-bridge, host-api, loader, tier, protocol, registry, bundle-integrity, bundle-signing, consent, shortcuts - Sub-features: Sandboxed iframe execution, postMessage RPC, tiered permissions (untrusted/privileged), admin approval gates, marketplace, dev-mode loading
F13 — Pro Multi-Tab Shell
- Routes:
/app/(main)/[locale]/pro/page.tsx - Components:
components/pro/(4 files): tab-bar, email-tab-body, compose-tab-body, interface-redirect - Store:
stores/pro-tab-store.ts(576 lines) — multi-tab management, split pane layout
F14 — Search
- Components:
components/search/(2 files): search-chips, advanced-search-panel - Backend:
lib/mail-index/(8 files) — encrypted SQLite/FTS5 index (Electron-only) - API:
app/api/offline/search,app/api/ai/retrieve - Coupling: Heavily coupled with email-store (search state lives there)
F15 — Authentication
- Lib:
lib/auth/(7 files): crypto, session-cookie, session-secret, verify-jmap-auth, pair-reauth, pairing-store, active-account-slot - Store:
stores/auth-store.ts(2033 lines) — THE hub; manages login, logout, token refresh, multi-account sessions - Sub-features: Basic auth, OAuth/OIDC with PKCE, demo mode, device pairing, TOTP 2FA, impersonation (JWT)
- API:
app/api/auth/(10 routes)
F16 — Setup Wizard
- Routes:
/app/(main)/setup/* - Lib:
lib/setup/(3 files): session, state, token - API:
app/api/setup/(6 routes)
Shared Substrate
S1 — State Management (Zustand stores)
- 28 store files in
stores/— Zustand withpersistmiddleware - Central hub:
auth-store.tsimports 7 other stores; bootstraps all feature stores after login - Bidirectional cycle:
email-store↔message-list-tabs-store - Anti-cycle pattern:
client-registry.ts— utility indirection to avoid auth-store cycles - Clean leaves:
locale-store,policy-store,toast-store,ui-store(0 dependencies)
S2 — JMAP Protocol Layer
lib/jmap/client.ts(7446 lines) — the communication backbone for ALL featureslib/jmap/types.ts(938 lines) — shared type definitionslib/jmap/client-interface.ts(358 lines) —IJMAPClientinterface (real + demo implementations)lib/jmap/transport-health.ts— failure counter for offline fallback gatinglib/jmap/request-limits.ts— request batching utilitieslib/jmap/search-utils.ts— search query building
S3 — Auth / Session / Multi-Account
lib/auth/*— AES-256-GCM cookie encryption, session lifecycle, device pairinglib/oauth/*— OAuth2/OIDC discovery, PKCE, token exchangelib/impersonation/*— JWT platform authlib/stalwart/*— server-side Stalwart auth context, JMAP passthroughstores/account-store.ts— multi-account registrystores/account-security-store.ts— TOTP 2FA, app passwords, API keys
S4 — Offline Replica (encrypted delta-sync mail store)
lib/offline-replica/(13 files): engine, sync (1122 lines), store (997 lines), apply, jmap, states, read, retention, errors, schema, types- SQLCipher-encrypted SQLite; delta sync via JMAP
/changes - Two-tier storage (envelope + body); cursor provenance with branded types
- Fallback only — consulted after live JMAP read fails; online session never sees replica data
S5 — Local Search Index (encrypted FTS5)
lib/mail-index/(8 files): store (534 lines), jmap (388 lines), extract (311 lines), reindex (385 lines), key (203 lines), paths, binding- SQLCipher-encrypted SQLite with FTS5, Electron/desktop only
- Event-driven (triggered by JMAP push changes via API routes)
- Key transport uses inherited fd (pipe), not env var
S6 — Platform Bridge
lib/electron-bridge.ts(55 lines) —isElectronShell(),showElectronNotification()lib/platform-capabilities.ts(29 lines) — single source of truth for platform feature availabilitylib/web-push.ts— web push for PWAelectron/main.ts(424 lines) — Electron main process (spawns Next.js server)electron/preload.ts(27 lines) — contextBridge, nodeIntegration=false, sandbox=true
S7 — Plugin Sandbox
lib/plugin-sandbox/(13 files) — iframe isolation, postMessage RPC, tier gatinglib/plugin-types.ts(1092 lines) — massive type definitionslib/plugin-hooks.ts— hook registration for email/calendar/composer/sidebar slotslib/plugin-loader.ts,lib/plugin-validator.ts,lib/plugin-storage.ts
S8 — HTTP Proxy / API Gateway
proxy.ts(211 lines) — Next.js middleware: CSP, nonce, setup-state gating, intl routingapp/api/*— 24 API endpoint groups (auth, admin, AI, offline, setup, etc.)
S9 — Security / Sanitization
lib/security/url-guard.ts(67 lines) — SSRF prevention, loopback/private-IP blockinglib/email-sanitization.ts— DOMPurify HTML sanitizationlib/smime-ca/(5 files) — S/MIME certificate authority (EJBCA + local dev CA)
S10 — Theme System
stores/theme-store.ts(593 lines) — light/dark/system, custom theme installationlib/theme-compiler.ts,lib/theme-loader.ts,lib/theme-logo.tslib/builtin-themes.ts— 8 built-in themes (2 shipping, 6 hidden)lib/color-transform.ts— luminance-based color remapping for dark mode
S11 — Internationalization
stores/locale-store.ts(19 lines)i18n/routing.ts— next-intl configurationlocales/— 24 language translationslib/jalali-utils.ts— Persian calendar support
S12 — Cross-Device Settings Sync
lib/settings-sync.ts— encrypted settings sync (AES-256-GCM)
S13 — Telemetry
lib/telemetry/(7 files) — anonymous heartbeat, opt-in, HMAC-hashed logins
S14 — Version Check
lib/version-check/(5 files) — polls version server, daily jittered schedulestores/update-store.ts(118 lines)
Feature Flag Registry (from lib/admin/types.ts)
| Flag | Default | Description |
|---|---|---|
pluginsEnabled |
false | Plugin system on/off |
pluginsUploadEnabled |
true | Allow manual plugin upload |
requirePluginApproval |
true | Admin must approve plugins |
themesEnabled |
true | Theme system on/off |
sidebarAppsEnabled |
true | Sidebar app visibility/order |
userThemesEnabled |
true | Users can install custom themes |
settingsExportEnabled |
true | Settings export |
customKeywordsEnabled |
true | Custom keyword/tag creation |
templatesEnabled |
true | Email templates |
calendarEnabled |
true | Calendar feature |
calendarTasksEnabled |
true | Calendar tasks |
smimeEnabled |
true | S/MIME |
externalContentEnabled |
true | External content in emails |
debugModeEnabled |
true | Debug tools |
folderIconsEnabled |
true | Custom folder icons |
hoverActionsConfigEnabled |
true | Hover action configuration |
filesEnabled |
true | File browser |
contactsEnabled |
true | Contacts |
crossAllViewEnabled |
false | Cross-account All Mail |
crossUnreadViewEnabled |
false | Cross-account Unread |
crossStarredViewEnabled |
false | Cross-account Starred |
unifiedCrossAccountEnabled |
false | Cross-account unified inbox |
aiAssistantEnabled |
true | AI assistant (visible by default — local ships free) |
Dependency / Coupling DAG (Store Layer)
auth-store (HUB) ──→ identity-store
├──→ account-store
├──→ calendar-store
├──→ contact-store
├──→ filter-store
├──→ settings-store
└──→ vacation-store
email-store ──→ calendar-store
├──→ auth-store
├──→ account-store
├──→ settings-store
└──→ message-list-tabs-store ←── (BIDIRECTIONAL CYCLE)
settings-store ──→ theme-store
└──→ locale-store
plugin-store ──→ locale-store
└──→ policy-store
theme-store ──→ policy-store
account-security-store ──→ auth-store
Standalone (14): file-store, task-store, filter-store, identity-store,
account-store, pro-tab-store, ui-store, vacation-store, totp-reauth-store,
calendar-notification-store, managed-account-store, update-store,
toast-store, admin-tab-store, template-store, webdav-store
Critical coupling paths to audit in Wave 2:
auth-store→ bootstraps 7 feature stores on login → any bootstrap failure cascadesemail-store↔message-list-tabs-store— bidirectional cycleemail-store→calendar-store— cross-feature coupling (invitation banners, birthday calendar)settings-store→theme-store→ theme injection affects ALL components- JMAP client (
lib/jmap/client.ts) → used by ALL feature stores viaclient-registry
Platform Parity Surface
| Feature | Web (PWA) | Electron Desktop | Native (planned) |
|---|---|---|---|
| Full | Full | Full (planned) | |
| Calendar | Full | Full | Full (planned) |
| Contacts | Full | Full | Full (planned) |
| Files | Full | Full | Full (planned) |
| Local Search Index | ❌ | SQLCipher FTS5 | ❌ (planned) |
| Offline Replica | ❌ | SQLCipher SQLite | ❌ (planned) |
| Native Notifications | Web Push | Electron Notification | Native (planned) |
| AI Local LLM | Browser CORS | No CORS (loopback) | Native (planned) |
| S/MIME | Full | Full | Full (planned) |