Conversation
ref https://linear.app/ghost/issue/HKG-1768/ The existing tests for posts-stats-service._enrichWithTitles only exercise the truthy branch — getResource() returning a valid resource. The falsy branch (path doesn't resolve, url_exists must be false) is the side most likely to regress when the call switches from the sync getResource() to the async facade.resolveUrl(). Pinning it here means the migration commit cannot land without preserving that contract.
Skip Ember test and hinting trees during normal development builds to speed up `pnpm dev` usage. This reduces dev server peak memory usage with ~1.1GB (~41%) and startup time with ~12s (~37%). The Ember `/tests` support available with `EMBER_INCLUDE_TESTS=true`.
no issue ## Summary - changed ShareModal from a monolithic props API into slot components for Root, Trigger, Content, headers, preview, actions, social links, and copy controls - updated PostShareModal and the onboarding share publication dialog to compose the new slots directly - moved data attributes onto ShareModal.Content so callers use normal JSX attributes instead of narrow prop bags - updated ShareModal stories and exported the preview props type ## Why The previous contentProps escape hatch leaked internal DialogContent details and required narrow type patches for data attributes. The slot API gives future share modal use-cases direct access to the DOM surfaces they need without growing a long list of pass-through props.
ref https://linear.app/ghost/issue/BER-3471/fix-default-members-list-query-performance-with-a-composite-index Added a composite index for the default members list query. With 2m members locally cold queries went from ~7s to ~0.1s and hot queries from ~0.8s to ~0.03s.
no-issue Node's MODULE_NOT_FOUND errors include a "Require stack" that lists the calling file's path. The existing heuristic checked whether the adapter path appeared anywhere in err.message to distinguish "adapter not found" from "adapter found but has a missing dependency." Because the adapter's own path always appears in the require stack, the check always matched, causing missing-dep errors to be silently swallowed and replaced with a misleading "Unable to find adapter" message. Restricting the check to the first line of the error message fixes the false positive.
no-issue These were removed in 8f2ec8d as knip flagged them unused, but SchedulingPro (the Ghost(Pro) scheduling adapter, copied in at release time) depends on both packages. Without them, Ghost(Pro) crashes at boot with "Unable to find scheduling adapter SchedulingPro."
ref https://linear.app/ghost/issue/BER-3615 ref #27624 ref #27703 - the "Paid subscriptions" bar chart and "Paid subscription breakdown" pie chart were counting gift redemptions as signups and gift end-of-life events as cancellations, which mixed gift activity into charts that are meant to track paid Stripe activity only - gifts now flow through their own member status (`gift`) and surface in the Paid members KPI tooltip instead, so duplicating them in the per- cadence/per-tier breakdowns double-counted activity and made the bar and pie charts disagree - reverted the gift-aware additions to subscription-stats-service so `/stats/subscriptions` returns paid Stripe deltas and counts only; `gift`→`paid` upgrades still appear via the regular paid subscription event flow once the trial converts and MRR begins
ref https://linear.app/ghost/issue/BER-3614/ ref #27759 - the "Paid subscription breakdown" pie chart was rendering a Complimentary slice derived from the comped delta, which mixed member-status semantics into a chart meant to track paid Stripe subscription activity only - this also caused the pie chart's total to disagree with the "Paid subscriptions" bar chart (which has always been paid-only); with this change the two charts tie out as the issue requires - comp activity is still surfaced in the Paid members KPI tooltip, so no signal is lost for sites that grant complimentary access - removed the now-pointless `calculateStatusSignups` helper that was generalised to support `gift`+`comped` during the gift work but only ever took `'comped'` after gifts moved to the backend
ref https://linear.app/ghost/issue/NY-1260 A quick change for more generic logging.
ref https://linear.app/ghost/issue/BER-3580/remove-ember-members-implementation The React members list now owns list, filter, import, and export flows, so the old Ember surface and unused dependencies can be removed.
…#27764) ref https://linear.app/ghost/issue/BER-3591/adding-a-second-label-filter-does-not-work The add-filter inline picker rendered multiselect fields (label, tier_id, offer_redemptions) as a sticky multi-select that stayed open after each click. Selecting a single value worked, but the open picker invited extra clicks, and each additional click left a stranded filter behind with stale single-value selections while a fresh filter was created with the latest cumulative selection. In multi-filter mode (allowMultiple={true}) the picker now treats multiselect fields as single-select: one click commits a new single-value filter and closes the picker. Multi-value editing of an existing filter continues through the filter row's own picker, which already supports live multi-select. Single-filter mode is unchanged.
towards https://linear.app/ghost/issue/NY-1260 This adds a temporary fake database with the automation tables we expect to add. Rather than doing proper migrations, we want to have this testbed we can use. (Of course, we'll do the proper migrations soon, once we've validated this design!) This should hopefully unblock building backend endpoints and therefore frontend UIs.
…and mobile (#27770) ref https://linear.app/ghost/issue/BER-3600/design-iteration - Simplified the selection screen when only one paid tier is available - Surfaced tier descriptions in the selection and details views - Pinned the gift card preview while details scroll - Removed "Powered by Ghost" branding from gift screens - Tightened spacing on the selection screen - Improved mobile styling across the 50/50 layouts - Replaced the expiry date on the gift card with gift value
closes https://linear.app/ghost/issue/BER-3616 When a recipient redeems a gift subscription, Ghost sends staff a notification email. Today it reuses the *paid subscription started* copy and the *New paid members* email-preference toggle. We've decided to differentiate the gift redemption staff notification more clearly from the new paid members one. This PR changes the copy of the gift redemption staff notification and moves it under the "Gift subscriptions" email-preference toggle. ## Changes **Email copy** — `notifyGiftSubscriptionStarted`: - Subject: `🎁 Paid subscription started: <name>` → `🎁 Gift subscription redeemed: <name>` - Headline: `You have a new paid subscriber` → `A gift subscription was redeemed` - Plaintext body line updated to match - Preview text updated to match **Notification preference** — gift redemptions now go through the existing **Gift subscriptions** toggle (Stripe + `giftSubscriptions` feature flag), instead of *New paid members*: - Toggle label: `Gift subscription purchases` → `Gift subscriptions` - Description: `Every time someone purchases a gift subscription` → `Every time someone purchases or redeems a gift subscription` **Pre-commit hygiene:** - Added a targeted `secretlint-disable-next-line` on the random-password-generator default in `user.js` — the new secret-scanning hook (added in #27609) flags `password: security.identifier.uid(50)` as a credential-assignment false-positive, blocking any future change to this file. The line generates a random password placeholder; it isn't a real credential.
ref https://linear.app/ghost/issue/HKG-1761/ The lazy URL service evaluates permalink templates against the resource itself (slug, published_at, primary_tag, ...). The old contract, `getUrlByResourceId(post.id)`, only carries an id; passing the full post object is the prerequisite for letting the lazy backend reach those fields without an extra DB lookup. Eager-mode behaviour is unchanged. Adds a tiny `toPlain(modelOrObj)` helper so callers can hand in either a Bookshelf model instance or an already-serialised hash and the URL helpers get a plain object regardless. Without this, `{...model}` would silently drop prototype-defined fields like `id` on Bookshelf models.
ref https://linear.app/ghost/issue/HKG-1763/ The facade gives every caller a stable, resource-based interface (`getUrlForResource(resource, options)`, `ownsResource(routerId, resource)`, `resolveUrl(path)`, `getResourceById(id)`) so we can later swap the eager precomputing UrlService for an on-demand backend without touching every caller. This commit only introduces the facade; subsequent commits move callers across, and HKG-1771 wires in the lazy backend behind a config flag. Eager behaviour is unchanged.
ref https://linear.app/ghost/issue/HKG-1764/ RouterManager now holds a reference to the facade rather than the raw eager UrlService, and the routing controllers (entry, channel, collection, static, taxonomy, previews, email-post) plus rss/generate-feed go through `routerManager.getUrlForResource(resource, ...)` / `ownsResource(routerId, resource)`. This is the first batch of caller migrations from the id-based `getUrlByResourceId` API to the resource-based facade — required so the lazy backend can later read permalink-template fields off the resource without hitting the DB twice.
ref https://linear.app/ghost/issue/HKG-1765/ `output/utils/url.js`'s `forPost`/`forUser`/`forTag` helpers now call `urlService.facade.getUrlForResource({...attrs, id, type}, ...)` instead of `urlService.getUrlByResourceId(id, ...)`. The explicit `id` is critical: Content API requests like `?fields=url` strip every attribute except url, so a plain `{...attrs, type}` would send an id-less resource and the eager facade's id-based fallback would return `/404/` for every record. Unit tests pin both the standard call and the stripped-attrs case so the regression class is caught at the serializer boundary.
ref https://linear.app/ghost/issue/HKG-1766/ `{{tags}}` / `{{authors}}` helpers and the `meta/url` and `meta/author-url` generators now go through `urlService.facade.getUrlForResource(resource, ...)` rather than `urlService.getUrlByResourceId(id, ...)`. Theme rendering hands us the full resource object, so spreading it into the facade is a direct fit and matches the contract the lazy backend will rely on. Drops three router-manager pass-throughs (`owns`, `getUrlByResourceId`, the no-longer-used routerManager-level `getResourceById` was kept on the facade for the entry controller's resource-type check) along with their last callers.
ref https://linear.app/ghost/issue/HKG-1767/ Last batch of `getUrlByResourceId(id)` callers in the backend: the slack notifier, the IndexNow notifier, the comments-emails service, and the audience-feedback service now call `urlService.facade.getUrlForResource` with a full resource. Each call site already has the model in hand so the spread is direct. After this commit no in-tree caller invokes `urlService.getUrlByResourceId` on the eager service; the legacy method only survives behind the facade as the eager fall-through implementation of `getUrlForResource`.
ref https://linear.app/ghost/issue/HKG-1768/ `url-translator.getTypeAndIdFromPath`, `posts-stats-service`, `content-stats-service`, and `mentions/resource-service` now consume the flat resource shape returned by `urlService.facade.resolveUrl(path)` instead of the legacy `{config: {type}, data: {...}}` envelope from `urlService.getResource(path)`. The translator and content-stats helper become async to match resolveUrl's contract; their direct callers are already in async contexts so the ripple stops there. The facade also resolves an inconsistency: the routing-level type ('posts'/'pages'/'tags'/'authors') wins over any DB type field on the underlying resource data, so the flat resource is unambiguous.
no ref Preview requests can include request-specific theme data, so these responses should not be stored or reused by frontend caches.
Ref https://linear.app/ghost/issue/BER-3598/ Adding a Shade calendar component and replacing the existing native datepicker for filtering with it. --------- Co-authored-by: Kevin Ansfield <kevin@ghost.org>
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 subscribe to this conversation on GitHub.
Already have an account?
Sign in.
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.
See Commits and Changes for more details.
Created by
pull[bot] (v2.0.0-alpha.4)
Can you help keep this open source service alive? 💖 Please sponsor : )