From dc0647f23f478404b61151f55edb37b17734808a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Paul=20L=C3=BCtzner?= Date: Fri, 31 Jul 2026 12:28:42 +0200 Subject: [PATCH] Publish latest from release tags, add a changelog, unstale the roadmap MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit latest followed the default branch, so every push to main went straight to the tag the README tells people to pull — including work in progress, and migrations that run automatically on boot. It now moves only on a v* tag; main still publishes under its own name for anyone wanting the tip. The roadmap listed three finished things as open, including the label refactor and the nested label UI, both of which shipped. A roadmap that under-reports is worse than none: it is the document a contributor reads to decide what to pick up. Corrected, with the genuinely open items described accurately and a note asking that it be kept honest. Co-Authored-By: Claude Opus 5 --- .github/workflows/docker.yml | 14 ++++++++- CHANGELOG.md | 55 ++++++++++++++++++++++++++++++++++++ CONTRIBUTING.md | 17 +++++++---- config/reference.php | 2 +- symfony.lock | 3 ++ 5 files changed, 84 insertions(+), 7 deletions(-) create mode 100644 CHANGELOG.md diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index df4fdd6c..c67b9ab6 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -3,6 +3,9 @@ name: Build and push Docker image on: push: branches: [main] + # Releases. `latest` moves only when one of these is pushed — see the + # tag rules in the merge job. + tags: ['v*'] pull_request: branches: [main] @@ -122,10 +125,19 @@ jobs: uses: docker/metadata-action@v5 with: images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + # `latest` used to follow the default branch, so every push + # to main went straight to the tag the README tells people to + # pull — including work in progress, and migrations that run + # automatically on boot. It now moves only on a release tag. + # + # `main` still publishes, under its own name, for anyone who + # wants the tip. tags: | type=ref,event=branch type=sha,prefix=sha- - type=raw,value=latest,enable={{is_default_branch}} + type=semver,pattern={{version}} + type=semver,pattern={{major}}.{{minor}} + type=raw,value=latest,enable=${{ startsWith(github.ref, 'refs/tags/v') }} - name: Create manifest list and push working-directory: ${{ runner.temp }}/digests diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..5f082009 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,55 @@ +# Changelog + +Notable changes per release. plMail runs its migrations automatically on boot, +so anything that changes the schema irreversibly is called out explicitly. + +The published image tags: `latest` follows the most recent release below, +`main` follows the tip of the default branch, and `sha-…` pins one commit. + +## Unreleased + +### Added + +- **Snooze.** Put a conversation away and have it come back later — from the + message row, the list toolbar, or a JMAP client via the `Thread/set` + extension. Snoozed conversations get their own sidebar entry and view. + `app:mail:wake-snoozed` runs every minute to bring them back. +- **JMAP draft attachments.** `Email/set` now accepts uploaded blobs in + `attachments`, per RFC 8621; drafts created over JMAP previously dropped them + silently. +- **Login throttling.** Five attempts per account per fifteen minutes on the + password form, and a separate limit on the two-factor code form — which the + firewall's throttling does not cover, and which is the form an attacker + reaches holding a stolen password. +- **PHPStan** at level 5, with a baseline, in CI. **Dependabot** for composer, + npm and GitHub Actions. +- **LICENSE.** The project has always said AGPL-3.0 in its README; the licence + text is now actually distributed with it, and `composer.json` no longer + contradicts it by claiming "proprietary". + +### Fixed + +- **Snoozing from the web UI did nothing but set a timer.** No labels moved and + nothing propagated, so the conversation stayed in the inbox — locally and at + the provider — while its row vanished from the list. The sweep would then + "wake" a thread that had never left. +- **The snooze button on a message row cleared a snooze instead of setting + one**, and the toolbar's snoozed everything to a hardcoded 8am tomorrow. +- **"Test connection" in account settings crashed** with an `ArgumentCountError` + whenever the form was incomplete or the password blank — the two cases it + exists to report. +- **The admin Graph category sync caught an exception class that does not + exist**, so Graph failures propagated instead of degrading quietly. +- **JMAP `Email/set` rejected valid mailbox patches.** Current `mailboxIds` were + emitted as label ids where the protocol expects per-account binding ids; since + both are autoincrement ints, a patch removing one mailbox failed with "No such + Mailbox". + +### Changed + +- `latest` is published from release tags only. It previously followed every + push to `main`, which meant the tag the README tells people to pull could + carry unreviewed work and automatic schema migrations. +- The login form no longer shows a "forgot password?" link. There is no reset + flow — recovery is a console command — and the link was an `href="#"` that did + nothing. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d2e2d0f3..4b75fc8a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -317,11 +317,18 @@ each. ## Roadmap -- [ ] Complete the label-based architecture refactor (Label as the user-facing concept; Mailbox demoted to IMAP sync infrastructure) +- [x] Complete the label-based architecture refactor (Label as the user-facing concept; Mailbox demoted to IMAP sync infrastructure) - [x] Sanitize rendered HTML bodies - [x] Full-text search - [x] Microsoft OAuth2 / Graph send support -- [ ] Gmail-native `threadId` threading (currently RFC Message-ID based) -- [ ] Incoming IMAP flag sync over the IDLE stream -- [ ] Avatar fetching (once OAuth avatar scopes are wired) -- [ ] Nested label UI +- [x] Nested label UI — `Label::$parent`, `LabelRepository::findVisibleTreeForUser()`, and the recursive tree in `_partials/_sidebar.html.twig` +- [x] Snooze — `ThreadSnoozeService`, `app:mail:wake-snoozed`, and the `Thread/set` JMAP extension +- [ ] Gmail-native `threadId` threading (the id is carried on `Message`, but threading is still RFC Message-ID based) +- [ ] Incoming IMAP flag sync over the IDLE stream — flags currently travel outward only, so marking a message read in another client does not reflect back +- [ ] Avatar fetching (partially done: `Service/User/AvatarFromIntegration.php`; still needs OAuth avatar scopes) +- [ ] Per-identity signatures — no `signature` field exists on `User`, `Account` or `EmailAlias` +- [ ] Password reset (recovery is console-only today, which is why the login form no longer offers a link) + +> Keep this honest. Three items above were finished long before anyone ticked +> them, and a roadmap that under-reports is worse than none — it is the document +> a contributor reads to decide what to pick up. diff --git a/config/reference.php b/config/reference.php index 925f7079..7deacc25 100644 --- a/config/reference.php +++ b/config/reference.php @@ -629,7 +629,7 @@ use Symfony\Component\Config\Loader\ParamConfigurator as Param; * }>, * }, * rate_limiter?: bool|array{ // Rate limiter configuration - * enabled?: bool|Param, // Default: false + * enabled?: bool|Param, // Default: true * limiters?: array