Sync with upstream Ghost v6.24.0#60
Open
madewithlove-machine-user wants to merge 291 commits intomainfrom
Open
Sync with upstream Ghost v6.24.0#60madewithlove-machine-user wants to merge 291 commits intomainfrom
madewithlove-machine-user wants to merge 291 commits intomainfrom
Conversation
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"`
…6647) closes https://linear.app/ghost/issue/ONC-1516 - When multiple `DELETE /members/{id}` requests target the same member concurrently, a race condition causes Bookshelf to throw "No Rows Deleted" errors that bubble up as 500s to Sentry - The `findOne` check passes for both requests (member exists), but by the time the second request's `destroy()` runs, the member is already gone - Fixed by passing `require: false` to Bookshelf's `destroy()` so it treats a missing row as a no-op — the member is already deleted, which is the desired outcome
ref f9f5f72 *This change should have no user impact.* The `announcementBar` feature flag has been enabled since 2023. We can remove the flag and assume it's always on.
closes https://linear.app/ghost/issue/BER-3336 The fallback to `sub.offer` was added as a transitional measure while core and admin could be out of sync during CD. Both are now fully deployed with `offer_redemptions` always populated, making the fallback dead code
Ref https://linear.app/ghost/issue/BER-3408/design-clean-up - Hid bulk actions for 0 selected members - Used text-foreground for columns with values for open rate
ref https://linear.app/ghost/issue/ENG-1326/ Ember's dynamic asset loading (lazy-loader, admin-x components, Koenig editor, user avatars) previously relied on a build-time `cdnUrl` config to construct absolute CDN URLs. This required piping GHOST_CDN_URL through environment.js into an encoded meta tag. Instead, derive the asset base at runtime from the Ember script's own URL — the same principle ES modules use for relative imports. If the script loaded from a CDN (via index.html sed rewrite), dynamic loads inherit the CDN origin. If local, they inherit the local path. New `assetBase()` utility replaces 6 duplicated cdnUrl conditionals. Also fixes the vite-ember-assets build to read from ghost/admin/dist.
ref https://linear.app/ghost/issue/NY-1132/sponsored-card-refinements-in-welcome-emails - "SPONSORED" title font size was massive in the welcome email editor
closes https://linear.app/ghost/issue/NY-1104 Moved the `welcomeEmailEditor` feature flag to GA.
fixes https://linear.app/ghost/issue/ONC-1512/ The contributor layout used `h-full overflow-auto` on the `<main>` element, with the EmberRoot child also using `h-full`. This constrained the Ember content to exactly the scroll container's height, so `.gh-viewport`'s `overflow: hidden` clipped the posts list with no way to scroll. Changed the contributor layout to use the same flex column pattern as the non-contributor `SidebarInset` layout — a `flex flex-col overflow-y-auto` container with a `flex-1` wrapper for children. This allows the content to grow beyond the container via `min-height: auto`, enabling scroll.
…ryGhost#26716) ref https://linear.app/ghost/issue/ONC-1512/ - Fixed the "Posts" link in the contributor user menu pointing to `/` instead of `/posts`, causing navigation to the wrong route
ref https://ghost.slack.com/archives/C018EKC56JF/p1771527734858719 The existing JWT is used for identity and only contains the email address. It has a TTL of 10 minutes which is reasonable for the use case but little else. We have another use case where integrations often want to validate the source, but are left with no option but to hit the members Admin API endpoint with an Admin API key because the email isn't sufficient to know if the member has access to a tier, is paid, isn't specific to email (which could change), etc., all of which are used for integrations. This endpoint would still require the members session cookie and has the following props, e.g.: ``` sub: darylmayer880479@example.com scope: members:entitlements:read member_uuid: 629c5190-f068-4f49-b533-00d7477a7aed paid: false active_tier_ids: [] jti: present iat: 1771555398 exp: 1771555518 aud/iss: http://localhost:2368/members/api Lifetime: 120s (2 minutes) ```
ref https://linear.app/ghost/issue/ONC-1533/ - replaced regex IP matching with a WHATWG URL parser normalization step and octet matching
ref https://linear.app/ghost/issue/ONC-1533/ The `gotOpts` passed to `metascraper-logo-favicon` only included a User-Agent header, meaning its internal requests (e.g. favicon probes via `reachable-url`) did not inherit the request hooks configured on `externalRequest`. Fixed by using `got.mergeOptions` to clone the full `externalRequest.defaults.options` (including hooks) and merge our User-Agent override on top
broccoli-asset-rev rewrites string literals in compiled JS at build time, prepending the CDN origin and fingerprint hash. The assetBase() utility introduced in TryGhost#26555 always prepends a non-empty base URL, which caused double-prefixing when the URL had already been made absolute by broccoli-asset-rev. Added prefixAssetUrl() helper that skips prefixing when the URL is already absolute, and updated all call sites.
ref https://linear.app/ghost/issue/ONC-1505/ The editor's settings view had two issues: - **Missing background color**: The content container had no `bg-white` class, making it transparent over the underlying admin UI and breaking the layout - **Infinite re-render loop**: `keywords={[]}` created a new array reference every render, causing `TopLevelGroup`'s `useEffect` to continuously re-register the component via `setVisibleComponents`, making the UI unresponsive
ref https://linear.app/ghost/project/ghost-cicd-e70bca221364 The trigger_cd dispatch was sending deployment policy fields (dry_run, should_rollout, environment) alongside artifact identity, which meant Ghost CI was dictating how Moya should deploy. This made it impossible to change deployment defaults without modifying Ghost. Stripped the payload to identity fields only (sha, ref, image_tag, pr_number, admin artifact IDs). Added a `deploy` flag that signals when the deploy-to-staging label is present — this is a fact about the PR, not a policy decision. Moya derives dry_run and should_rollout from pr_number presence and the deploy flag.
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
ref https://linear.app/ghost/issue/BER-3496/table-broken-on-mobile-sizes - The members list layout was broken on mobile. The table cells were collapsed because of a incorrect widths.
no ref This should be a performance-only change. This patch makes the following changes: 1. Only read `.hbs` files once, and only compile the Handlebars template once. 2. Read `.hbs` files in parallel, for performance. 3. Small: don't re-convert a string unnecessarily. (We were reading it as a UTF-8 string, then converting it back to a buffer, then re-reading that buffer as a UTF-8 string.) 4. Small: don't keep useless references around.
no ref **What?** This enables [TypeScript's `erasableSyntaxOnly` option][0] across the codebase. This means we can't use enums, namespaces, or a few other things. This change should have no user impact. **Why?** The big reason: [Node supports running TypeScript files directly][1], but only if all the syntax is erasable. This means we can remove build steps in many cases! It has a few other advantages, too: simplifying source maps, restricting some tricky parts of TypeScript, slightly accelerating TypeScript compilation, This doesn't enable some of the other options that are sometimes recommended in tandem, like `verbatimModuleSyntax`. Nor does it actually run `.ts` files with Node. Those are for another day. [0]: https://www.typescriptlang.org/tsconfig/#erasableSyntaxOnly [1]: https://nodejs.org/api/typescript.html#type-stripping
no refs ## Summary - Expanded the `create-database-migration` skill description to improve triggering accuracy - Skill now activates for broader set of user requests: referencing schema.js, knex-migrator, migrations directory, "add a field/column", or Linear issues requiring schema changes - Also added a missing step to update the integration tests, which the agent missed the first time. - With this change, the agent was able to one-shot the addition of an add table migration, with all tests passing in CI on the first try.
ref https://linear.app/ghost/issue/PLA-10/ - adds an OfferFactory to the e2e data-factory layer - moves offer creation and update call sites off the old offer service - removes the old helpers/services/offers helper
…st#26966) closes https://linear.app/ghost/issue/NY-1163 _I recommend reviewing this [commit-by-commit](https://github.com/TryGhost/Ghost/pull/26966/commits) rather than all at once._ _This change should have no user impact._ This lets the welcome email renderer match the newsletter email renderer's design settings. For example, it now fetches the background color the same way. It's currently hard-coded, but it should be easy to add real values on top of this. In addition to automated tests, I manually verified that welcome emails' HTML was unchanged.
no ref - Fixes a regression of bookmark cards in emails having unnecessary underline.
no ref - removes the thin Stripe checkout-initiation smoke spec from the top-level e2e suite This path is now covered more directly through product-facing e2e tests instead of a separate harness-oriented smoke test. Overlapping coverage already exists in: - e2e/tests/admin/settings/portal-settings.test.ts - e2e/tests/public/portal-tiers.test.ts - e2e/tests/public/portal-upgrade.test.ts
closes https://linear.app/ghost/issue/NY-1137/create-email-design-settings-table-migration ## Summary Adds a new `email_design_settings` table to store email design/styling settings independently from newsletters. The table columns mirror the design-related columns from the `newsletters` table (background colors, fonts, button styles, image corners, etc.) with identical types, constraints, and defaults. `title_alignment` and `post_title_color` are intentionally omitted — this table initially supports automated emails which don't use those columns. They can be added later if/when we extend templates for newsletters. ## Changes - Added `email_design_settings` table definition in `schema.js` with `isIn` validations for enum fields - Added `addTable` migration for the new table - Added `email_design_settings` to the backup tables list in `table-lists.js` - Updated exporter test expectations and schema integrity hash --------- Co-authored-by: Troy Ciesco <tmciesco@gmail.com>
ref e7737ce This tells Cursor how to initialize a worktree. See [the docs][0]. [0]: https://cursor.com/docs/configuration/worktrees#initialization-script
no ref There are a number of places we can add that are type checking in this file. This is a types only change that should have no user impact.
ref https://linear.app/ghost/issue/BER-2416/starter-customers-cannot-obtain-a-content-api-key - Starter plan customers can't create custom integrations, which means they have no way to obtain their Content API key - Added a "Content API" card to the built-in integrations tab that opens a modal showing the Content API key and API URL - Follows the same modal pattern as Zapier and other built-in integrations
ref https://ghost.slack.com/archives/C0A0YQ2S53R/p1774572762467409 - marked transistor integration as locked/disabled when limited by host; overrode related backend settings - updated sort behavior of integrations (disabled sorts to bottom) Host plans may limit custom integrations via blocking webhooks. This currently happens for Zapier and was not extended to Transistor, which needs different handling given it has both externally set up webhooks + native Ghost functionality. We now disable the Ghost-related functionality based on the host settings which is consistent with the webhook behavior.
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.24.0.