Real end-to-end verification (Playwright-driven real Electron app on this
Mac, against the actual local Ollama instance, not a mock) found the actual
blocker: production's connect-src CSP (`'self' https: wss:`) rejects plain
http:// entirely, so lib/ai/local-client.ts's loopback fetch to Ollama never
even attempted the network in a production build - Electron or browser
alike. This is almost certainly what looked like a browser-sandbox network
issue in the earlier (non-Electron) QA pass tonight too.
Fix is narrow, not a blanket http: relaxation: connect-src now additionally
allows `http://127.0.0.1:*` and `http://localhost:*` specifically. Loopback
has no network hop, so it doesn't reopen the mixed-content-style downgrade
risk the existing https-only production policy guards against - unlike
dev's blanket `http:` allowance, which stays dev-only.
Confirmed fixed: rebuilt (build:standalone + build:electron), launched the
real Electron app via Playwright's _electron, and got a genuine answer back
from the real local Ollama - "Test connection" showed Reachable (the real
success state, not the CORS-diagnostic fallback text), and asking "Reply
with exactly the words: LOCAL AI WORKS" returned exactly that, with the
correct "no local mail index in this session" banner alongside it (accurate
for a fresh Electron session with nothing synced yet).
Also flips FeatureGates.aiAssistantEnabled's default false->true: local now
genuinely works and ships free/unmetered (see lib/ai/types.ts), so there is
a real feature behind the tab, not an empty preview - matches tonight's
explicit "I want AI visible" instruction. An admin can still turn it off.
Verified: typecheck clean, lint clean (0 errors, pre-existing warnings
only), translations pass (48/48), full production build succeeds.
Two real bugs in the previous WS-push commit, both found while building the
integration test for it (not theoretical - each reproduced and verified
before and after the fix):
1. proxy.ts's production CSP (`connect-src 'self' https:`) has no `wss:`
term, so `new WebSocket(...)` was blocked before any network attempt at
all - confirmed by listening for `securitypolicyviolation` against the
real reference server (stalwart.sandbox.vnc.de, HTTPS): the WS feature
was entirely inert in a production build, for every server, not just
ones with an incompatible auth model. Fixed by adding `wss:` alongside
`https:` in production - no new trust surface, since `https:` here
already allows fetch/XHR to any TLS host (needed for
ALLOW_CUSTOM_JMAP_ENDPOINT / multi-server setups), so extending that same
model to WebSocket is consistent, not a new precedent. Verified after the
fix: the same probe now reaches the network and gets a real (expected)
auth rejection from Stalwart instead of a CSP block.
2. lib/jmap/client.ts's circuit breaker (5 attempts, 1s/30s backoff) could
take up to ~31s to give up on WS and fall back to SSE. Against a server
that fails the handshake instantly and deterministically every time (the
auth-header limitation documented in the previous commit), that's ~31s
of NO live push at all - WS hasn't succeeded and hasn't given up yet, so
SSE never starts connecting, and any mail delivered in that window was
silently missed (SSE only streams changes from the moment it connects,
no catch-up). Reproduced directly: a real SMTP delivery sent during that
window never reached the notification bridge.
Fixed two ways:
- Tightened the ladder to a 200ms base / 5s cap / 3-attempt circuit
breaker (worst case ~1.75s instead of ~31s) - still genuine
exponential-with-jitter backoff, just tuned for a failure mode that's
fast and deterministic rather than slow and flaky. A slow/real
network issue is unaffected: a hanging attempt is still bounded by
the browser's own WebSocket connect timeout, not by these constants.
- setupPushNotifications() now primes a polling baseline
(fetchCurrentStates()) in parallel with the WS attempt, and
fallbackFromWebSocket() diffs against it (checkForStateChanges())
BEFORE connectSSE()/startPollingFallback() get a chance to erase that
opportunity. This is what actually closes the gap rather than just
shrinking it: it catches a change that happened to the primary
account during the (now much shorter) WS retry window.
electron/main.ts also gets a test-only escape hatch (ELECTRON_LOAD_URL): set
it to skip spawning the standalone server and load that URL instead. Real
users and every packaging/CI path never set it - added because verifying
the fixes above against this repo's own local Stalwart fixture (deliberately
plaintext HTTP - integration/webmail.Dockerfile makes the identical
trade-off for the browser-based suite) needs a dev-mode Next.js server
(proxy.ts only widens connect-src for plain http/ws in dev), not the
production standalone build electron/main.ts normally boots.
next.config.ts: added 127.0.0.1 to allowedDevOrigins alongside the existing
LAN entry - electron/main.ts always loads its window at 127.0.0.1, so a
dev-mode Electron run (only used by the escape hatch above) needs it in this
allowlist the same as any other cross-origin dev client would.
Verified: full lib/__tests__ JMAP suite still green (158/158); npm run
test:electron still green (4/4); the raw WebSocket probe against the real
sandbox now reaches the network post-fix instead of being CSP-blocked.
The page CSP set font-src 'self', which blocked fonts referenced by
rendered email CSS (brand webfonts loaded over https). Allow https:/data:
for font-src. Email bodies render inside the sandboxed iframe, which keeps
its own stricter blocking-mode font-src for privacy.
The root (main)/layout renders <html> ABOVE the [locale] segment, so next-intl's
getLocale() returns the default locale there - emitting <html lang="en"> on every
page regardless of UI language (e.g. /de) and a hardcoded English <head>
description. Both are strong "translate this page" triggers in Chrome.
proxy.ts already exposes the nonce to server components via the
x-middleware-request-* mechanism; expose the request pathname the same way as
x-pathname, and have the root layout derive the locale from it (falling back to
getLocale() when the path has no locale segment) for both <html lang> and the
localized meta_description (new key in all 17 locales; the English value is
unchanged).
* Added account selection for protocol links when multiple connected accounts are available, including mailto: links
* Added support for handling mailto: links in an already-open PWA/session instead of always opening a new tab
* Added webcal: protocol handling for calendar links
* Added account selection for webcal: links when multiple calendar-capable accounts are connected
* Added an import-or-subscribe choice for detected webcal calendars
* Added protocol handler settings for registering mail and calendar handlers and choosing the open mode
* Added service worker/session coordination for passing protocol requests between browser/PWA contexts
* Added tests and translations for the new protocol handler flows
When localePrefix is 'always' (or 'as-needed' with a non-default locale),
paths like /en/settings already have the locale in the URL. Running them
through the next-intl middleware a second time can trigger rewrite loops,
especially when combined with a proxy basePath where the middleware's
detection of the 'current' path conflicts with the rewritten one.
Skip the intl middleware in this case — the path is already in the
canonical locale-prefixed form and no further rewriting is needed.
This makes NEXT_PUBLIC_LOCALE_PREFIX=always reliable for sub-path
deployments.
Completes full internationalization coverage for the webmail application with enhanced language support:
- Replace all remaining hardcoded English strings with translation keys
- Add timezone auto-detection to prevent hydration mismatches
- Enable instant client-side language switching without page reload
- Add 60+ new translation keys in English and French locales
- Improve language switcher component with proper state management
- Add health check endpoint for container orchestration
All user-facing text now supports English and French with no hardcoded fallbacks, providing a fully localized experience.
A modern, privacy-focused webmail client built with Next.js and the JMAP protocol.
Designed for Stalwart Mail Server.
Features:
- Full email operations (compose, reply, forward, threading)
- Real-time push notifications
- Dark/light theme support
- Mobile responsive design
- Keyboard shortcuts
- Drag-and-drop organization
- i18n (English/French)
- Security-first (external content blocked, HTML sanitization)