Sync with upstream Ghost v6.23.0#59
Open
madewithlove-machine-user wants to merge 275 commits intomainfrom
Open
Sync with upstream Ghost v6.23.0#59madewithlove-machine-user wants to merge 275 commits intomainfrom
madewithlove-machine-user wants to merge 275 commits intomainfrom
Conversation
…ryGhost#26672) closes https://linear.app/ghost/issue/BER-3406 - previously, we expected the /tiers endpoint to always have tier-related data to render retention offer popups - however, archived tiers are not returned as part of the `/tiers` endpoint - with this fix, we directly get necessary data from the member subscription object
ref TryGhost#26416 Fixed `SharedStorage` deprecation warning
Added label picker with inline editing to members filter ref https://linear.app/tryghost/issue/BER-3342/ The members list label filter now uses a custom label picker built with Popover + Command (cmdk) from Shade. Labels can be created, renamed, and deleted directly from both the filter dropdown and the bulk-action modals (add/remove label), without leaving the members page.
Fixed DST drift in domain warming integration test clock no ref The test used setDate(), which diverged from elapsed-24h day math around DST and caused flaky mysql8 CI failures.
ref TryGhost#26655 Addresses review feedback from TryGhost#26655 and fixes additional issues found during follow-up testing. **Review comment fixes:** - Added `aria-selected="false"` to loading option in `power-select-options-with-scroll.hbs` to fix a11y lint violation instead of suppressing it - Fixed `gh-member-single-label-input` to not overwrite a preselected label with the first available option when the selected label isn't in the first loaded page — only calls `onChange` for default (no pre-selection) case - Changed `options.unshift` to `options.push` in `gh-members-recipient-select` so the Labels group appears after Tiers, keeping infinite scroll predictable - Added `@tracked` to `_meta` in `labels-manager` so `hasLoaded`/`hasLoadedAll` getters reactively update - Added `parseInt` to pagination comparison in `loadMoreTask` for consistency with `hasLoadedAll` **Additional fixes:** - Used public `labelsManager.labels` getter instead of private `labelsManager._labels` in `gh-member-label-input` - Switched `addSearchedLabels` deduplication to ID-based `Set` lookup (reference equality can fail with Ember Data models) - Made `searchLabelsTask` restartable to prevent stacking concurrent search requests - Removed redundant `|| []` after `.filter(Boolean)` in `selectedLabels` - Added explicit `waitFor` calls in E2E label selection helpers to prevent flaky tests with paginated dropdown loading
ref https://linear.app/ghost/issue/ONC-1525/ - Improved field matching in `rejectPrivateFieldsTransformer` on public content API endpoints - Extracted the transformer into a shared utility
no issue - Added `contents: read` permission to `job_setup` so checkout works in private repos (the implicit public repo read access doesn't apply here) - Replaced hardcoded `TryGhost/Ghost` repo reference with `github.repository` in the PR metadata API call - Added `github.repository == 'TryGhost/Ghost'` guard to all deploy/publish jobs so they skip cleanly on other repos: canary, publish_packages, deploy_tinybird_staging, deploy_tinybird_production, deploy_admin, trigger_cd, and create-release-branch - Consolidated Docker image push strategy: non-main repos use artifact-based image transfer (same path as fork PRs) so E2E tests still work without GHCR access - Renamed `is-fork-pr`/`is-fork` to `use-artifact` throughout ci.yml and load-docker-image action to accurately reflect the broadened semantics
ref https://linear.app/ghost/issue/NY-1127/ - made urls full instead of relative, which won't work in email
…ost#26702) closes https://linear.app/ghost/issue/BER-3402/ - Removed `ignorePunctuation: true` from label client-side sort to match server ordering - `ignorePunctuation` caused `#`-prefixed labels to sort as if the `#` didn't exist, but the server's `name asc` puts `#` before alphabetical characters - This mismatch meant each new page of labels loaded via infinite scroll appeared in the wrong position instead of appending to the list
TryGhost#26697) no ref Node can natively run TypeScript files, but only if they have "erasable syntax". This means it can't handle things like enums and namespaces. This enforces that in the `@tryghost/parse-email-address` package.
closes https://linear.app/ghost/issue/NY-1128 closes https://linear.app/ghost/issue/NY-1121 closes https://linear.app/ghost/issue/NY-1120 - The editor didn't have enough contrast in dark mode - Font sizes and spacings were overridden in the editor toolbar - Scrolling was attached to the editor area instead of the whole inner container - Missed some padding at the bottom - Safari had clipping issue Co-authored-by: Evan Hahn <evan@ghost.org>
towards https://linear.app/ghost/issue/NY-1125 Test-only change. Improves the reliability of these tests somewhat.
…ost#26691) no ref _I recommend [reviewing this with whitespace changes disabled](https://github.com/TryGhost/Ghost/pull/26691/changes?w=1)._ I wrote this package so I should review changes to it.
no ref - a test for labs ensures that config values take precedence over GA keys. However, the test referenced the `announcementBar` flag was removed in TryGhost#26196. That means this test was looking at a state that wasn't really true - instead, we can check for the first item in the `GA_KEYS` list. If there's nothing in the list, the test can be skipped. - a few other tests had this same issue, so this PR updates them as well - there was a test for alpha flags that wasn't actually doing anything, so it's deleted
no ref I wrote this package so I should review changes to it.
ref https://linear.app/ghost/issue/NY-1123 ref TryGhost/Koenig#1754  Co-authored-by: Steve Larson <9larsons@gmail.com>
closes https://linear.app/ghost/issue/NY-1125 - added new `usePinturaConfig` hook to admin Co-authored-by: Evan Hahn <evan@ghost.org>
closes https://linear.app/ghost/issue/BER-3393 Aligned the copy for once offers with the repeating / free months offers: - Before "$4.00 - Next payment" - After: "$4.00/month - Ends {date}"
ref https://linear.app/ghost/issue/BER-3388/design-bugsimprovements - Fixed empty price gap in retention offer screen for free month discounts — the price div was rendering even when not applicable, causing a large visual gap - Fixed title overlapping with back/close buttons in non-full-size retention offer - Improved responsive layout for the offer list table - Added `aria-label` to the icon-only filter button in retention offers
ref https://linear.app/ghost/issue/BER-3348/portal-paid-section-doesnt-indicate-subscription-is-canceled When a member cancels their subscription, the Portal account page looked nearly identical to an active subscription — the only signal was a small plain-text line that was easy to miss. - Replaced the plain cancellation text with an accent-colored banner containing the expiry date and "Continue subscription" button - Added a CANCELED badge next to the tier name in the plan row - Added i18n keys for the new copy - Added unit tests for both the banner and badge <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > UI-only changes to account display plus new tests/i18n strings; no backend logic or sensitive flows modified. > > **Overview** > Improves the Portal account UI for *canceled-but-still-active* paid subscriptions by replacing the easy-to-miss expiry notice with a prominent brand-accented banner showing the expiry date and a **Continue subscription** CTA. > > Adds a `Canceled` badge next to the tier/plan name, introduces styling for the new banner/badge, and includes i18n keys plus new unit tests covering the banner, expiry date rendering, and badge visibility. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 39b77fe. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->
…ngs (TryGhost#26696) closes https://linear.app/ghost/issue/BER-3392 closes https://linear.app/ghost/issue/BER-3218 - added max. length display title / description fields - added inline validation for floating numbers for free-months / duration-in-months fields --------- Co-authored-by: Sodbileg Gansukh <sodbileg.gansukh@gmail.com>
The canary job dispatched to Ghost-Moya's deploy.yml which cloned Ghost from source and built a Docker image from scratch. trigger_cd now handles all cases by dispatching to cd.yml which extends the pre-built GHCR image instead. trigger_cd supports the deploy-to-staging label on PRs, matching the old canary behaviour: PRs from org members or renovate[bot] with the label deploy to staging via cd.yml. Main builds dispatch with dry_run=false and should_rollout=true. Regular PRs remain dry-run only. deploy_admin remains enabled and unchanged — it continues to update production admin-forward via deploy-admin.yml.
ref https://linear.app/tryghost/issue/BER-3380/ When bulk-unsubscribing members, the modal now lets you choose between unsubscribing from all newsletters or selecting specific ones via an inline searchable picker. When only one newsletter is active, the modal shows a simple confirmation instead.
…ts (TryGhost#26699) refs https://linear.app/tryghost/issue/ONC-1529 - The paginated label loading refactor (TryGhost#26655) missed adding server-side search to `gh-members-recipient-select` and `gh-members-segment-select` - Users with many labels had to manually scroll through all pages before search could find labels not yet loaded client-side - Added conditional server-side search (via `labelsManager.searchLabelsTask`) to both components, matching the pattern already used by `gh-member-label-input` - Tiers, statuses, and offers are still filtered client-side since they're always fully loaded - Fixed labels found via search result not being selectable in some cases --------- Co-authored-by: Steve Larson <9larsons@gmail.com>
closes https://linear.app/ghost/issue/BER-3413/account-page-date-mismatch-between-discount-end-billing-period - When a member redeems a 1-month free offer, Stripe sets the discount start date to now and end date to now + 1 month, i.e. anchors them to the redemption date, not the billing date - In Portal, to avoid confusion, we want to anchor the `"- Ends {date}"` to a billing date to avoid confusion - For example, today is 5 Mar 2026 and my subscription renews on 3 Apr 2026. I hit cancel, redeem a free-month offer that applies to my next payment. I expect to see `"$0.00/month - Ends 3 Apr 2026"`, not `"$0.00/month - Ends 5 Apr 2026"`
ref https://linear.app/ghost/issue/PLA-9/ - adds fake Stripe coupon support for offer-backed checkout sessions - records checkout discounts and carries them into fake subscription state - adds a minimal admin offers helper for top-level e2e setup - adds a thin offer checkout smoke test to prove the capability end to end
DuckDuckGo Tracker Radar scores navigator.vendor, navigator.platform, and navigator.maxTouchPoints as high-entropy fingerprinting APIs. Since comments-ui loads from cdn.jsdelivr.net (which has a domain-level fingerprinting score of 3), Safari's Advanced Fingerprinting Protection — on by default for all users since Safari 26 — restricts API access and storage for our script, breaking the comment editor. This Vite build plugin replaces those API accesses in ProseMirror and tiptap dependencies with navigator.userAgent-only equivalents at build time. The patched detection produces identical results for all major browser/OS combinations, with the only difference being iPadOS 13+ (which already sends a desktop Mac UA and works fine with desktop handling). Includes equivalence tests covering 13 browser fingerprints and a bundle scan test that verifies the built UMD output contains none of the blocked APIs.
Changed the condition from `github.repository_owner == 'TryGhost'` to `github.repository == 'TryGhost/Ghost'` so that forks owned by the TryGhost org cannot inadvertently trigger the Ghost(Pro) CD pipeline.
ref https://linear.app/ghost/issue/PLA-9/ - migrates the remaining Portal offer redemption coverage into top-level e2e - adds the Portal offer page object and redemption flow support - deletes the legacy browser offers spec
towards https://linear.app/ghost/issue/NY-1163 This change should have no user impact. Just fixes a type error when this test function was used.
no ref Before this change, `foo #ff9900` was considered a valid hex color. After this change, it is not. I believe it's difficult to have bad data in the database, but if that happens, we want to check it properly.
…ryGhost#26960) ## Problem `@site.admin_url` was using `getAdminUrl()` which returns the base admin domain (e.g. `https://admin.example.com/`) **without** `/ghost/`. This means the "Site owner login" link on the private page pointed to the domain root instead of the admin panel for any site with a separate admin URL configured (including all Ghost(Pro) sites). ## Fix Changed `update-local-template-options.js` to use `urlFor('admin', true)` which correctly appends `/ghost/` to the admin URL. ## Test Added a test that stubs `getAdminUrl()` with a separate domain (`https://admin.example.com/`) and asserts the resulting `admin_url` ends with `/ghost/`. This test fails on main and passes with the fix.
no ref
- adds stable Portal offer-page test ids for title, discount label,
message, and updated price
- simplifies the Portal offer page object to expose plain locators
instead of text-specific helper methods
- hides paid-tier Stripe readiness behind createPaidPortalTier(...,
{stripe})
- reduces repeated assertion blocks in portal-offers.test.ts with local
expectation helpers
- teaches the e2e Playwright lint rule to recognize local expect...
assertion helpers
closes https://linear.app/ghost/issue/NY-1178 towards https://linear.app/ghost/issue/NY-1163 This was written almost entirely by Claude Opus 4.6 against the following prompt: > I want to create a snapshot test for member welcome emails. We currently have _some_ tests for this, like in `ghost/core/test/e2e-api/admin/automated-emails.test.js` and `ghost/core/test/integration/services/member-welcome-emails.test.js`, but these don't assert on the final rendered output. > > Build a (test-only) change that renders the full HTML of a member welcome email, and asserts on the snapshot. We have `assertMatchSnapshot` for this purpose, which I reckon you should use. I reviewed the code thoroughly and made a couple of small tweaks.
ref https://linear.app/ghost/issue/FEA-480 - adds the `show_share_button` column to newsletters to prep for future share button functionality - includes the bare minimum for setting up the migration and for tests to pass. In this case it includes updating snapshots related to the newsletter - also bumps to `6.23.0-rc.0`
no ref Released new transistor.fm integration, enabling customizable Portal account settings re: podcast RSS feeds and new embed/editor card support in the editor.
closes https://linear.app/ghost/issue/NY-1105 This change should have no user impact. All it does is remove the flag, enabling the feature for everyone. In addition to automated tests, I manually verified that the new welcome email editor shows up.
https://linear.app/ghost/issue/BER-3466 - Stripe does not return a discount end date for once offers - As a result, the next payment object was returning a null discount end date for once offers - As a result, clients (Portal, themes) had to differentiate between types of offers (once offers expire at the end of the current billing period, repeating offers at discount end, forever offers never) - We want to abstract away this business logic, so that Portal and themes don’t have to know nor worry about the differences between types of offers. Instead, if the next payment object exposes an end date, then it’s a limited-time offer with an end date (once, repeating). If not, it’s an offer without time limit (forever offer) Code sample from themes before: ``` {{#if next_payment.discount}} <!-- {{next_payment.discount.duration}} --> <s>{{price plan}}/{{plan.interval}}</s> <p> {{price next_payment}}/{{next_payment.interval}} {{#match next_payment.discount.duration "=" "forever"}} — Forever {{/match}} {{#match next_payment.discount.duration "=" "repeating"}} — Ends {{date next_payment.discount.end format="D MMM YYYY"}} {{/match}} {{#match next_payment.discount.duration "=" "once"}} — Ends {{date current_period_end format="D MMM YYYY"}} {{/match}} </p> {{else}} <p>{{price plan}}/{{plan.interval}}</p> {{/if}} ``` Code sample from themes after: ``` {{#if next_payment.discount}} <s>{{price plan}}/{{plan.interval}}</s> <p> {{price next_payment}}/{{next_payment.interval}} {{#if (next_payment.discount.end)}} — Ends {{date next_payment.discount.end format="D MMM YYYY"}} {{else}} — Forever {{/if}} </p> {{else}} <p>{{price plan}}/{{plan.interval}}</p> {{/if}} ```
ref https://linear.app/ghost/issue/BER-3466 - The next_payment object now exposes a discount end date for limited-time offers (once, repeating) and null for unlimited offers (forever) - This simplifies the logic for displaying the offer duration in Portal: display "Ends - {date}" if discount end is present, "Forever" otherwise
ref https://linear.app/ghost/issue/BER-3498/dropdown-improvements - Search field for finding options was missing in the filter dropdown. This is especially important for great UX when paid-memberships are turned on. - The height of the filter dropdown was too small. Making it larger helps scanning options fast.
) ref https://linear.app/ghost/issue/PLA-10/ - Migrated the staff invite acceptance test from `ghost/core/test/e2e-browser/portal/invites.spec.js` to `e2e/tests/admin/settings/staff-invites.test.ts` - The old file had two identical tests — the "2FA invite" variant never enabled 2FA and was a duplicate. Replaced both with a single test. - Uses Mailpit to extract the invite URL from the email instead of direct database access via `models.Invite.findOne()`
ref https://linear.app/ghost/issue/BER-3440 Bumps comments-ui to 1.4 which patches out fingerprinting API usage by the comment editor library we're using. This should prevent 1.4 being flagged as fingerprinting and being blocked by fingerprint blocking extensions and browsers.
ref https://linear.app/ghost/issue/BER-3501/improve-avatars - Avatars in the members list were falling back to the default user icon instead of the initials with a colored background.
…UID (TryGhost#26745) no ref When running Ghost in development with `yarn dev` in a remote VM, the analytics app crashes when loading over a plaintext http connection because `crypto.randomUUID` is only available in a secure context (https or localhost). `crypto.randomUUID` is also overkill for React keys anyways, so this changes to using integers instead, to make it easier to develop Ghost in a remote VM environment.
The workflow has two fundamental problems: (1) admin deploys are global per-environment, not per-site, so deploying a PR's admin overwrites admin-forward/ for ALL staging sites, and (2) any merge to main triggers a full staging rollout that overwrites both server and admin immediately. The deployment only lasts until the next merge to main, making it unreliable.
ref https://linear.app/ghost/issue/ONC-1591 The browse endpoint for settings did not correctly filter integration keys based on user role, causing them to be included in the response for contributor-level users.
ref https://linear.app/ghost/issue/ONC-1593 The /authentication/setup GET endpoint was returning the owner user's name and email even after setup had completed. Now it only returns the setup status boolean once the site is already configured.
ref https://linear.app/ghost/issue/ONC-1592 The invite acceptance flow was not wrapped in a transaction, which meant the invite token was not atomically consumed alongside user creation. Wrapped the operation in a transaction so the invite is properly marked as used.
ref https://linear.app/ghost/issue/ONC-1594 The urlToPath method could return a path outside the expected storage directory when the input URL contained relative segments. Added path normalization to ensure the resolved path stays within the configured storage root. Applied the same fix to the S3 storage adapter for consistency.
ref https://linear.app/ghost/issue/PLA-10/ - Migrated 3 valuable tier tests (create, update with portal verification, archive/unarchive) from `ghost/core/test/e2e-browser/` to `e2e/tests/admin/settings/tiers.test.ts` - Added `TiersSection` page object with reusable tier CRUD methods (create, edit, archive, unarchive, enable in portal) - Dropped 2 redundant tests (default prices smoke check, tier+offer creation already covered by `portal-offers.test.ts`)
no ref Hardened GitHub Actions shell usage to remove direct expression interpolation.
ref https://linear.app/ghost/issue/PLA-10/ - Adds a fake Mailgun HTTP server (following the fake Stripe server pattern) that accepts newsletter email batches from Ghost and forwards personalized emails to MailPit for test assertions - Adds `mailgunEnabled` fixture to configure Ghost's `bulkEmail.mailgun.*` settings to point at the fake server - Adds `emailClient` fixture providing a shared MailPit client to any test - Extends `PublishFlow` page object with `selectPublishType()` and `confirm()` methods - Includes a smoke test that publishes a newsletter post and verifies email delivery end-to-end via MailPit
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.
Syncing fork to upstream release
v6.23.0.