diff --git a/CHANGELOG.md b/CHANGELOG.md index 794c51a4..4579685d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,51 @@ # Changelog +## 1.6.2 (2026-05-06) + +### Features + +- **Plugins**: Hot-reload and dev-folder loading for live plugin development +- **Plugins**: On-demand `src/` bundling via esbuild +- **Plugins**: New `http:fetch` permission and `httpOrigins` manifest field +- **Plugins**: `onBeforeEmailSend` hook with `fromEmail` exposed on `OutgoingEmail` +- **Plugins**: Project `EmailReadView` for the email-banner slot and expose auth results +- **Plugins**: Ingest icon, banner, and screenshots from the source repo +- **Plugins**: Restrict plugin and theme install/uninstall to the admin dashboard +- **Mail**: Multi-server JMAP support +- **Settings**: Fulltext search across the settings sidebar +- **Settings**: Sub-result rows with highlight in settings search +- **Settings**: Surface plugin settings as search sub-results +- **Settings**: Remove experimental tags from themes, plugins, and sender favicons +- **Viewer**: Redesigned external-mail banner above attachments +- **Calendar**: Calendar invitation banner expands on row click +- **Calendar**: Calendar invitation banner is now collapsible + +### Fixes + +- **Admin**: Collapse admin panel into a single tabbed page +- **Plugins**: Inline plugin configure panel to avoid dev-mode hang +- **Plugins**: Resolve `PLUGIN_DEV_DIR` plugins in admin config route +- **Plugins**: Add missing body type assertion in `createPluginAPI` fetch options +- **Plugins**: Propagate `settingsSchema` +- **Settings**: Highlight plugin and theme cards in search results +- **Settings**: Open plugin card on first click of a setting sub-result +- **Settings**: Drop ghost sub-results from account and language search +- **Settings**: Improve search highlight styling +- **Viewer**: Show notification banners above attachments +- **Viewer**: Rework S/MIME banner to match calendar invitation +- **Viewer**: Close PDF preview on Escape before email viewer +- **Viewer**: Render PDF previews via `` with `blob:` in object-src CSP (#253) +- **Calendar**: Align invitation icon with sender avatar column +- **Calendar**: Fix invitation picker clipping (#250) +- **Auth**: Read `activeAccountId` from authStore in account selectors +- **UI**: Adjust toast item border radius and progress bar styles +- **UI**: Remove fly-in animation from context menu submenus +- **i18n**: Add missing Czech flag icon + +### i18n + +- Add missing translation keys across 15 locales + ## 1.6.1 (2026-05-04) ### Features diff --git a/Dockerfile b/Dockerfile index e91af29a..20084be2 100644 --- a/Dockerfile +++ b/Dockerfile @@ -9,7 +9,7 @@ ENV NEXT_TELEMETRY_DISABLED=1 ARG NEXT_PUBLIC_BASE_PATH= ENV NEXT_PUBLIC_BASE_PATH=$NEXT_PUBLIC_BASE_PATH # Commit SHA shown in the About screen. .dockerignore excludes .git, so -# `git rev-parse` inside the build can't find it — CI must pass it in. +# `git rev-parse` inside the build can't find it - CI must pass it in. ARG GIT_COMMIT=unknown ENV GIT_COMMIT=$GIT_COMMIT RUN npx next build --webpack diff --git a/app/[locale]/login/page.tsx b/app/[locale]/login/page.tsx index 9708799a..2b974a63 100644 --- a/app/[locale]/login/page.tsx +++ b/app/[locale]/login/page.tsx @@ -543,7 +543,7 @@ export default function LoginPage() { const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); - // Server-list entries always win — `allowCustomJmapEndpoint` is only honored + // Server-list entries always win - `allowCustomJmapEndpoint` is only honored // when the admin hasn't configured a server list. const effectiveServerUrl = selectedServer?.url || (allowCustomJmapEndpoint ? jmapEndpoint : serverUrl); diff --git a/app/[locale]/page.tsx b/app/[locale]/page.tsx index c0de511b..74fa0bc1 100644 --- a/app/[locale]/page.tsx +++ b/app/[locale]/page.tsx @@ -740,7 +740,7 @@ export default function Home() { // System-notification click handler. The push SW navigates the user back // here with `?email=` (specific email it built the toast from) or - // `?openLatestUnread=1` (generic "New mail" toast — happens when the + // `?openLatestUnread=1` (generic "New mail" toast - happens when the // preview API failed). We resolve those params once after the inbox has // finished loading and open the right message, then strip the params so a // refresh doesn't re-open it. @@ -1610,7 +1610,7 @@ export default function Home() { const originalEmailId = selectedEmail.id; - // RFC 5322 §3.6.4 threading — keep the conversation stitched together (#234). + // RFC 5322 §3.6.4 threading - keep the conversation stitched together (#234). const threading = computeReplyThreadingHeaders({ messageId: selectedEmail.messageId, references: selectedEmail.references, @@ -1674,7 +1674,7 @@ export default function Home() { } // Show the list stub immediately so subject/sender render without - // waiting for the body fetch — avoids the loading flicker. + // waiting for the body fetch - avoids the loading flicker. const listEmail = emails.find(e => e.id === email.id); if (listEmail) { selectEmail(listEmail); diff --git a/app/[locale]/settings/page.tsx b/app/[locale]/settings/page.tsx index c4fe8c39..602dd0ee 100644 --- a/app/[locale]/settings/page.tsx +++ b/app/[locale]/settings/page.tsx @@ -491,7 +491,7 @@ export default function SettingsPage() { // For plugin-setting sub-results, ask the plugins tab to expand the // matching card so the field becomes part of the DOM. Dispatched here // (not in the click handler) because PluginsSettings only mounts after - // the tab switches, and its listener registers in its own useEffect — + // the tab switches, and its listener registers in its own useEffect - // child effects run before parent effects, so by the time we get here // the listener is guaranteed to be in place. if (pendingHighlight.pluginId) { @@ -534,7 +534,7 @@ export default function SettingsPage() { // First attempt next frame so the freshly-mounted tab content is in DOM. const raf = window.requestAnimationFrame(tryHighlight); - // Do NOT reset pendingHighlight here — that would retrigger this effect + // Do NOT reset pendingHighlight here - that would retrigger this effect // and the cleanup below would strip the class right after we added it. return () => { cancelled = true; diff --git a/app/admin/plugins/[id]/page.tsx b/app/admin/plugins/[id]/page.tsx index e9688133..dd7ae43d 100644 --- a/app/admin/plugins/[id]/page.tsx +++ b/app/admin/plugins/[id]/page.tsx @@ -1,6 +1,6 @@ import { redirect } from 'next/navigation'; -// Inline panel handles plugin config now — see _tabs/plugin-config-panel.tsx. +// Inline panel handles plugin config now - see _tabs/plugin-config-panel.tsx. // Old deep links land on the plugins tab; the user clicks the gear again. export default function Page() { redirect('/admin?tab=plugins'); diff --git a/app/api/admin/version/route.ts b/app/api/admin/version/route.ts index 813beeb2..bbeb4b26 100644 --- a/app/api/admin/version/route.ts +++ b/app/api/admin/version/route.ts @@ -43,7 +43,7 @@ export async function GET() { /** * POST /api/admin/version - * { action: 'check-now' } — force a fresh upstream fetch. + * { action: 'check-now' } - force a fresh upstream fetch. */ export async function POST(req: NextRequest) { try { diff --git a/app/api/auth/session/route.ts b/app/api/auth/session/route.ts index 9b206fc5..0e054004 100644 --- a/app/api/auth/session/route.ts +++ b/app/api/auth/session/route.ts @@ -44,7 +44,7 @@ export async function POST(request: NextRequest) { // caller cannot point this route at internal hosts. We accept the global // `jmapServerUrl` and any entry from `jmapServers`. When neither matches, // we fall back to the request URL only if `allowCustomJmapEndpoint` is on - // — and even then the URL must resolve to a public address. + // - and even then the URL must resolve to a public address. await configManager.ensureLoaded(); const configuredServerUrl = configManager.get('jmapServerUrl', '') || diff --git a/app/api/auth/totp-token-exchange/route.ts b/app/api/auth/totp-token-exchange/route.ts index f97bd6e5..85f2025d 100644 --- a/app/api/auth/totp-token-exchange/route.ts +++ b/app/api/auth/totp-token-exchange/route.ts @@ -91,7 +91,7 @@ export async function POST(request: NextRequest) { // Pin the upstream URL to a configured JMAP server. The list of allowed // servers is `jmapServerUrl` plus any entry from `jmapServers`. Only when // no server is configured (and the deployment explicitly allows custom - // JMAP endpoints) do we fall back to the user-supplied URL — and even then + // JMAP endpoints) do we fall back to the user-supplied URL - and even then // it must resolve to a public address. await configManager.ensureLoaded(); const configuredServerUrl = diff --git a/app/api/push/preview/route.ts b/app/api/push/preview/route.ts index 2d18b44a..5188479d 100644 --- a/app/api/push/preview/route.ts +++ b/app/api/push/preview/route.ts @@ -163,7 +163,7 @@ export async function GET(request: NextRequest) { }, }); } catch (error) { - // `fetch failed` from undici is too generic to debug — the real reason + // `fetch failed` from undici is too generic to debug - the real reason // (ENOTFOUND, ECONNREFUSED, TLS error, …) is on `error.cause`. const err = error as Error & { cause?: { code?: string; message?: string } }; logger.error('push preview failed', { diff --git a/components/email/email-composer.tsx b/components/email/email-composer.tsx index 86b15ce9..1bb8a74c 100644 --- a/components/email/email-composer.tsx +++ b/components/email/email-composer.tsx @@ -963,7 +963,7 @@ export function EmailComposer({ return ''; }; - // RFC 5322 §3.6.4 threading — only continues the chain on a reply, not a forward. + // RFC 5322 §3.6.4 threading - only continues the chain on a reply, not a forward. const threadingHeaders = (mode === 'reply' || mode === 'replyAll') ? computeReplyThreadingHeaders(replyTo) : null; diff --git a/components/email/email-viewer.tsx b/components/email/email-viewer.tsx index 53e0f374..16aafcb9 100644 --- a/components/email/email-viewer.tsx +++ b/components/email/email-viewer.tsx @@ -2697,7 +2697,7 @@ export function EmailViewer({ const colorScheme = isDark && emailHasNativeDarkMode ? 'light dark' : 'light'; // Bare HTML emails (no