Repository navigation
v0.3.9 - #283
Merged
Merged
v0.3.9#283
Conversation
…ches A release's work is now collected on a milestone branch (milestone/v0.4.0) before it reaches main, and the suite only ran on pull requests into main, so nothing proposed for the milestone was tested. `milestone/**` joins `main`, and `wip/**` comes along from wip/28, where it already runs the suite on the phases of #28 — the same block on both, so they merge without a conflict. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> (cherry picked from commit d009ff7)
… branch (#254) GitHub links an issue from "Closes #N" only in a pull request into the default branch. Into milestone/v0.3.8 or a wip/ branch the closing references stay empty, so the check failed on every such pull request however its description named the issue (#252 was the first). For a pull request into another branch, the description is now read for the same keywords GitHub accepts. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Releasing asked for the E2E workflow to be started by hand on main before a tag. Since main became protected, the Release vX.Y.Z pull request cannot merge until that workflow is green on it, and its commit is what is tagged, so a manual run only ran the same jobs twice. Releasing now says so, and to update the branch if main moved after its checks ran, since the protection does not ask for up-to-date branches. The paragraph in CONTRIBUTING and the header of e2e.yml that said main is not verified commit by commit describe main before it was protected; both now describe it as it is. Closes #251 Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…, a live now line (#256) The month chip is its title first, on up to two lines, then its time range and place; a narrower day drops the end and the place for a third line of title, long words are hyphenated, and an unanswered invitation has a dashed bar. Below 39rem, where a day is bars, the grid shrinks and the chosen day is listed in full under it: today at first, any tapped day after, with a + for that day that the toolbar's + follows too. On a wider touch screen the first tap opens a day's card and the second its entry (calendar--month-tap, and a day-open: variant that the hover and the tap share). The calendar toolbar fits below 37rem with icons, a short date label and smaller buttons, and under 22rem today's date in a box for Today. English writes a same-month week as "Oct 20-26" (range_short_month). The top bar tightens below 640px and moves Sync now, What's new and Send feedback into the user menu. Dialogs are the whole screen below 768px, as compose is. calendar--now moves the now line every minute on the view's clock and redraws a view that shows the old day or the new one when the date changes, which also moves the month's circle on today. And a + on a day that has gone opened the editor on today: DefaultEventStart::onDay pulled any past day to the next hour. A past day now keeps its date. Closes #253 Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
#260) GitHub acts on "Closes #N" only for merges into the default branch, so issues fixed on a milestone branch stayed open until the release reached main. A workflow on merged pull requests into milestone/** now closes the issues the description names, with a comment linking the pull request. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…isible (#258) The list preview flattened the whole body and skipped short blocks to find the first long one, so a one-line reply previewed as its attribution line or the quoted mail. The quoted history (same seam as the reading view's "···") and a "-- " signature are cut off first now, in HTML and plain text, and the chrome skip only applies to bulk mail. The JMAP preview uses the same code. The composer folded the quote with an inline display: none and its clean-up removed only the toggle, so replies went out with an invisible quote. The fold is removed in the editor and again on the server, and the reading view reveals a folded quote in mail already received. Closes #257 Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…#267) EmailPatchApplier wrote seenAt for $seen and left MessageThread::$unreadCount alone, so the web list (which draws bold from that counter) disagreed with the phone app. $seen now goes through ThreadStatusUpdater::markRead(), the web UI's own path, and markRead() counts the whole thread rather than the messages passed in. Closes #266 Co-authored-by: Philippe Delodder <7259755+phdelodder@users.noreply.github.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…#270) Trashing a conversation with a message already in the bin queued that message as a move from Trash into Trash. webklex's move() then left the connection in EXAMINE and the server refused it ("NO Not permitted in EXAMINE state"), and the row had already lost its UID. ApplyImapFlagsHandler no longer sends a move into the folder a message is in (and gives the row its UID back), and ThreadStatusUpdater's trash(), archive() and restore() leave such messages out of the move. Closes #269 Co-authored-by: Philippe Delodder <7259755+phdelodder@users.noreply.github.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…272) A thread's labels are the union of its messages', so one deleted message put Trash on the thread and took the whole conversation out of the Inbox, its tab, label lists, All Mail, Unread, the badges and search, along with any reply threaded into it afterwards. The bin rule is now "carries Trash and has no message outside it", written once (binnedThreads()) for every list and badge and spelled out the same way in the search SQL. Option (b) of the issue. Closes #268 Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
expandedValueChanged() used closest('div') to reach the editor's wrapper, but closest() starts at the element itself and the editor is a div, so the wrapper never became a flex container and the editor stayed 170px tall, clipped by [contain:paint]. The wrapper is now the flex column and the editor grows (1 0 auto) to fill the window or its content; the plain-text field fills the expanded window too.
Closes #262
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…#275) Every pull request added its paragraph under ## Unreleased in CHANGELOG.md at the same place, so the second of any two conflicted with the first once it merged, and each conflict fix re-ran the whole E2E suite. A pull request now adds changelog.d/<issue>-<words>.md; bin/changelog-release.sh, run in the Release vX.Y.Z pull request, collects the fragments (highlights first) and anything still under ## Unreleased into the release's section and deletes them. CHANGELOG.md stays the one file What's new, bin/release-notes.sh and GitHub read. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Every failure in the enable path (registering the service worker, the browser's push subscription, the server's answer) ended in the same "Something went wrong" and the exception was dropped, so a report from a Pixel 8 could not say which. The pane now names the step and the DOMException name or HTTP status, and logs the exception to the console. Closes #277 Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Closed
…AV contacts in it (#282) A contacts page and pane for plMail's own contacts and the ones on a server: Google and Outlook on the mail account's own sign-in, Nextcloud through the Nextcloud connection, any CardDAV server through Settings -> Contacts. Every field a phone keeps, the photo included, syncs both ways. Duplicates merge with 90 days to undo; vCard and CSV import and export; contacts suggested while composing; JMAP Contact/get, /query and /set. Nextcloud calendars come with the Nextcloud connection too. Replaces #237 and #109 by @jo2904 and #244 by @DanversKara. Closes #157 Closes #236 Co-authored-by: jo2904 <jo2904@users.noreply.github.com> Co-authored-by: DanversKara <DanversKara@users.noreply.github.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
PECL is deprecated in favour of PIE and was down for most of an evening on 2026-10-21, failing every image build. PIE 1.5.2 now installs APCu (and Xdebug in the dev image) from the extensions' own releases; it is mounted for that one step and not left in the image. Nothing changes at runtime. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Contact photos were stored in var/contacts, outside the volumes the web and worker containers share, so synced photos answered 404. They now live under APP_STORAGE_DIR/uploads/contacts; a migration clears the lost paths and has synced books read again so the photos come back. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Addresses are stored lower-cased but were matched as the provider spelled them, so a pull removed and re-inserted the same address and broke the unique key (prod sync failure). They are now matched in their stored form. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
A click on one of a contact's reference emails in the docked contacts pane was a Turbo visit inside the contacts frame, which blanked the mail pane. The click is now offered to the mail pane as a mail-pane:open event; the pane opens the message beside the contact. Without a mail pane (the full contacts page) it stays an ordinary visit. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
The full Contacts page was a page of its own, so a reference email clicked there was a visit: the contact was gone, and on a notebook the mailbox came back on the calendar it remembered. The full page is now the mailbox with data-contacts-full on the shell: the mail column put away and the contacts dock given the row, with the page's own content in the dock's frame. Opening a message from it loads the message into the existing mail pane, then contacts--pane#reveal drops the state in a view transition: the mail slides in from the left and the dock narrows to its width, the same DOM throughout. On a notebook the calendar gives way; on a wide screen it stays. The reference links lose data-turbo-frame="_top", which made ui--split stash a calendar position for a visit that never came. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Clicking an image or video attachment chip opens a viewer in the modal
instead of downloading. Previewable chips (PDF included) get a download
half on the right. Images load from the existing attachment route; videos
from a new /mail/attachment/{id}/video route that serves a fixed video
type inline under the sandbox CSP and answers Range requests.
Closes #295
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
MessageEraser loaded a message's parts to delete their files, then removed only the message. After the delete Doctrine forgot the message but kept the parts managed, so the next flush in the same unit of work failed with "a new entity was found through the relationship MessagePart#message". Every sync that removed server-deleted mail with an attachment failed that way. The eraser now removes the parts, removes a thread it empties (announced to JMAP as destroyed), and logs file-delete failures instead of keeping the row. MessagePurger hands its local delete to the eraser, so "delete forever" gains the thread-label re-sync it lacked. ImapAccountSyncer clears an entity manager a failed folder left open, so one folder's refused flush no longer fails the rest. Closes #299 Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…AI (#305) (#306) New contacts start in quick add: paste a name, signature, address line or vCard and the details it finds are listed, each with a one-click type. The editor shows the name as one field whose parts fill themselves (titles and degrees included), rows that appear as you type, Paste details for existing contacts and a Save to picker. Contact help is a new AI feature, off by default, that rereads pasted text and suggests updates from the signature of a contact's newest mail. Phones are read with libphonenumber. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Cards read out of mail in Spam are left off the Happening Soon panel and the mailbox strip (read-time filter, so moving the mail out brings them back), and a spam conversation no longer shows its cards above the message. The bin keeps its cards. Search and the type-ahead leave Spam out unless asked with in:spam, as they already did for the bin. A spam conversation's toolbar gets a Not spam button that moves it to the inbox. Closes #304 Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
) * Show the pictures a review request hid, dropped or never asked for A BAUHAUS review request, sent through Bazaarvoice, showed neither its logo nor its product photos, with images allowed. The image proxy was fine; three steps around it were not. - The logo is linked as http://. The sanitizer keeps http sources and leaves them to render time, the fetcher only connects over TLS, and nothing in between upgraded the URL, so every http image became the placeholder. RemoteContentBlocker now signs the https:// form. The fetcher's own rules also say in the log which one refused a target; until now that refusal was the one silent path. - The first product card sits inside a <form>, and dropElement('form') took the photo, the name and the star links with it. The form is unwrapped now; textarea and select still go with their content. - Each card is class="d" style="display:none", switched on with .d {display:block !important}. CssToInlineStyles keeps an inline property whatever the rule says, so the card stayed hidden next to its Outlook twin. Html5CssInliner now lets a stylesheet !important beat a plain inline style, and matches selectors against forms as the divs the reader will see, so `form .a` rules take the sender's fallback for clients without forms. app:backfill safe-html-hidden-cards re-sanitizes stored mail with a form or a stylesheet !important. One task for both, because neither fix brings the card back alone. Closes #271 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Mark the German mail page as translated from the current English one Both pages gained the same two paragraphs in the previous commit, but the translated-from hash still named the English page before them, so DocumentationCoverageTest reported the translation as stale. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Credit the image proxy entry and keep it as a changelog fragment Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Paul Lützner <karatektus@gmail.com>
… actions on a phone (#314) On a phone, below md and by touch only: a row swipes right for read/unread and left for archive, each direction set under Settings → Mail → Swipe actions (read/unread, archive, star, nothing; never trash), and holding a row opens an action sheet with every action its strip offers. Mouse, trackpad and wider windows are unchanged. Takes over #117, #118 and #119, whose fork was archived. Also gives the Contacts settings' two cards their gap. Co-authored-by: Flouss <15911342+Flouss@users.noreply.github.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…address can be changed later (#279) CalDAV and Calendar feed get their own group below the file services, with the shared provider row and Configure form; Nextcloud stays with the file services. Calendar services are offered to everyone, so their rows say so and the form has no switch. The server address accepts only http or https, up to 512 characters. Once people have connected, the CalDAV form says how many connections a changed or cleared pin reaches. A calendar feed offers its address only while one is stored, so it can be cleared. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…as new (#319) Two connections to the same server, a Nextcloud one and a CardDAV one made by hand, reach the same address book under the same remote id. Settings → Contacts now offers such a book locked, saying which connection has it, and refuses a posted tick, as it does for calendars. Otherwise every contact in it showed twice. Closes #317 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…tach, and a switch to turn it off (#204) Closes #203 Closes #205 - A sibling's IMAP copy keeps the placement its folder gives it; Gmail's INBOX/SENT/DRAFT/TRASH/SPAM no longer move it (by @phdelodder). - Gmail deleting its copy detaches the sibling's IMAP copy instead of erasing it. - app:backfill gmailify-placement repairs copies the old merge had moved. - Settings → Aliases: a Gmailify switch per Gmail account. Off detaches carried copies in a background job and declines mail Gmail still collects for another account (Bcc'd mail excepted, as documented). Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Part of #223: newGmailIds() asks only about the ids Gmail just listed, and nothing when none were. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
# Conflicts: # CHANGELOG.md
karatektus
marked this pull request as ready for review
October 22, 2026 17:52
When the worker couldn't connect to the Gmail or Graph API at all, the account sync went up Messenger's retry ladder: a WARNING with three nested stack traces per attempt, then CRITICAL and a parked job if the outage lasted. That job resumes from a cursor and is asked for again every quarter hour, so nothing was ever lost. WaitOutConnectionFailures now puts a SyncAccountMessage that failed only with an HttpClient TransportException back on its own queue, on the ingest ladder's delays, with one info line per attempt. After five waits it is dropped with a single warning. Message batches and every other failure keep the normal path. Closes #332 Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Brings the v0.3.9 milestone from
milestone/v0.3.9tomain. Every piece was reviewed and merged as its own pull request into the milestone branch, with the full suite run on each.The milestone is complete. Every issue in it is closed; #202 moved to v0.4.1.
main, carrying the v0.3.8 release, is merged in (9636ca68, a CHANGELOG conflict resolved by keeping## Unreleasedabove## v0.3.8).Next: once this is merged, a
Release v0.3.9pull request collectschangelog.d/into the v0.3.9 section (bin/changelog-release.sh v0.3.9). Its commit onmainis the one to tag.Pull requests in the milestone
Earlier ones (#252, #254, #256 and others) merged while the branch was still
milestone-v0.3.8.Issues closed in the milestone
#134, #147, #148, #149, #157, #158, #203, #205, #212, #223, #236, #251, #253, #257, #262, #266, #268, #269, #271, #277, #293, #295, #296, #299, #304, #305, #309, #311, #317. The milestone-branch workflow closed them as their pull requests merged; this PR is labelled
deploymentforlinked-issue.🤖 Generated with Claude Code