- Remove isMobile/isTablet JS state from layout classes to eliminate
orientation-change blink (CSS breakpoints apply instantly)
- Extract dismissViewer() helper for return-to-list pattern (was duplicated 4x)
- Convert more-actions dropdown from hover-only to click-toggle (works on touch)
- Add Escape key and role attributes to more-actions menu
- Reset showMoreActions on email change to prevent stale menu
- Move overflow-x:auto to iframe inner body stylesheet (was inert on iframe element)
- Reset tabletListVisible when crossing to desktop breakpoint
P0 Core:
- Sandboxed iframe rendering for rich HTML emails (CSS isolation)
- API retry with exponential backoff for transient JMAP failures
- Mobile bottom action bar with touch-friendly email actions
- Long-press context menu + tap-to-expand submenus on touch devices
P1 Polish:
- Tag counts sidebar section with JMAP batch queries
- Empty folder option for Junk/Trash with batch delete
- Extra-compact density option (28px rows, 44px on touch)
- SPF/DKIM/DMARC security tooltips with plain-language explanations
- Resizable sidebars with drag, touch, and keyboard support
- Expandable sender info panel in email viewer
All 8 locales updated (en/fr/ja/es/it/de/nl/pt).
New OAUTH_ONLY env var hides username/password fields and promotes the
SSO button as the primary login method. Useful for deployments that
want to enforce OAuth-only authentication.
Includes retry button when OAuth discovery fails to avoid dead-end
login pages.
Closes#32
Mobile bottom navigation bar was hidden when viewing an email, trapping
users in the mail section. Now always visible on mobile.
Move-to-folder submenu now shows hierarchical folder structure using
buildMailboxTree/flattenMailboxTree instead of a flat list.
Closes#30, closes#29
parseDuration() now handles undefined event.duration gracefully instead
of crashing on .match(). CalendarEvent.duration type updated to reflect
that JMAP servers may omit this field.
Sieve filter activation replaced isActive (server-set per RFC 9661) with
onSuccessActivateScript. Create/update calls merged with activation into
single JMAP roundtrips.
Closes#31, closes#21
The context menu's "Move to folder" submenu was practically unusable:
scroll events inside the folder list closed the entire menu, and moving
the mouse from the trigger to the submenu dismissed it instantly.
Additionally, the JMAP moveEmail call silently discarded server errors
and always used the primary account ID, causing moves to appear to
succeed locally while the server rejected them.
Closesroot-fr/jmap-webmail#19
Split instrumentation into two files so the Edge bundler never sees
Node.js-specific imports (fs, process.cwd). The thin entry point
conditionally imports the Node-only module via NEXT_RUNTIME check.
Upgrade minimatch 9.0.6→9.0.9 and 3.1.3→3.1.5 to fix high-severity
ReDoS via combinatorial backtracking in matchOne() with multiple
non-adjacent GLOBSTAR segments.
Move the toggle button above the expandable details section so it stays
at the same position regardless of expand/collapse state. Also replace
hardcoded English text with i18n translations.
Closes#18
Remove npm/npx from runner stage (unused at runtime) to eliminate
minimatch and tar CVEs. Upgrade Alpine packages to patch busybox
and zlib vulnerabilities.
Basic Auth sessions now persist across page refreshes via an encrypted
httpOnly cookie (AES-256-GCM). The feature is opt-in: admin sets
SESSION_SECRET env var, user checks "Remember me" at login.
Rework the 2FA input from a checkbox+animated panel to a discreet
"I have a 2FA code" text link that reveals the TOTP field on click.
Logout now revokes the refresh token and redirects to the IdP's
end_session_endpoint so the SSO session is fully terminated. The
end_session_url is validated server-side (HTTPS only) before being
returned to the client, preventing open redirect attacks.
Also adds 14 unit tests covering PKCE (including RFC 7636 test vector)
and OAuth discovery (fallback, caching, required field validation).
Support external IdPs (Keycloak, Authentik) that handle auth separately
from the JMAP server via OAUTH_ISSUER_URL. Also surfaces discovery
failures with logged errors and a visible warning banner instead of
silently hiding the SSO button.
undoSpam and batchUndoSpam were passing the store's composite mailbox
ID instead of the JMAP originalId, which could cause the move to
silently fail for shared mailboxes.
Add OAuth2 authorization code flow with PKCE as an alternative to
basic auth. Supports Stalwart's built-in OAuth provider with automatic
token refresh, session persistence via httpOnly cookies, and graceful
fallback to basic auth when OAuth is not configured.
When Stalwart returns session URLs with explicit :443 port, the URL
parser normalizes it out but the original string keeps it, causing
string slicing to produce wrong offsets. Now extracts the path by
finding the first / after // instead of relying on origin length.
Sent emails only included the bare email address in the From field,
so recipients saw <user@example.com> with no display name. Now the
identity name from the JMAP server is included in the From header
for composed emails, quick replies, and saved drafts.
Also sorts identities so the one matching the login username is
selected by default in the composer dropdown.
JMAP servers (e.g. Stalwart behind Docker) may return internal hostnames
in their session response (https://jmap/ instead of the actual server URL).
Replace the origin of all session URLs (apiUrl, downloadUrl, uploadUrl,
eventSourceUrl) with the user-configured server origin, using string
slicing to preserve RFC 6570 template placeholders without encoding.
Fixes#16
When the JMAP server is reachable but lacks CORS headers, users saw
"Unable to reach the server" which is misleading. Now a no-cors probe
distinguishes CORS blocking from true network failure, surfacing a
specific message that points users to their server's CORS config.
Remove jmap-jam (unused), @types/dompurify (bundled in dompurify 3.x),
eslint-config-next (not used in flat config), and lint-staged (unreferenced).
Add @eslint/js and eslint-plugin-react-hooks as explicit devDependencies
since they were phantom deps that would break without their former hosts.
Replace isEdit references with direct event checks inside the useCallback,
since isEdit is derived from event which is already in the dependency array.
The 15-second polling refresh was replacing the entire emails array on every
state change detection, even when the fetched data was identical. This caused
unnecessary re-renders of the virtualized thread list. Now compares email IDs,
threads, and keywords before updating state.
When editing a recurring event and clearing its recurrence, the update
patch was omitting recurrenceRules entirely instead of setting it to null.
The server never received the change. Also clear recurrenceOverrides and
excludedRecurrenceRules so orphaned exceptions don't persist. Same fix
applied to locations and alerts removal.
Fix "this_and_future" scope overriding recurrence removal by reordering
the spread so user updates take precedence over master's original rules.
Closesroot-fr/jmap-webmail#14
Address GitHub issue #13 (events not spanning real duration, hour label alignment)
and add five standard calendar UX features:
- Click-drag on empty time slots to create events with pre-filled time range
- Resize events by dragging bottom edge handle (15-min snap, optimistic JMAP update)
- Recurring event edit/delete scope dialog (this/following/all occurrences)
- Double-click quick create with inline title input (PT1H default)
- Event duplication button in modal (clones +1 day, opens for editing)
Shared interaction logic extracted to hooks/use-time-grid-interactions.ts.
All features include i18n (8 locales), accessibility (ARIA, pointer events),
and proper error handling with toast feedback.
Automate multi-arch Docker image builds (amd64+arm64) on push to main
or version tags via GitHub Actions. Add OCI metadata labels for GHCR
discoverability. Update release script to sync infra files to
public-release branch.
- Add ConfirmDialog component with promise-based useConfirmDialog hook
- Add WelcomeBanner onboarding component with localStorage persistence
- Add NavigationRail (desktop icon rail + mobile bottom tab bar)
- Polish login form (shake on error, TOTP slide, password visibility, session expired banner)
- Add inline form validation with shake animation in email composer and contacts
- Add empty state patterns for contacts (no data vs no search results)
- Improve toast system with undo action support and typed durations
- Add template settings tab to settings page
- Refactor sidebar (cleaner code, remove unused imports)
- Add WCAG AA reduced-motion, safe area insets, sr-only live region
- Add shake/slide animations in globals.css
- Update i18n for all 8 locales