Conversation
closes https://linear.app/ghost/issue/BER-3541 - Existing free members who redeem a gift subscription now receive the paid welcome email — previously only new gift signups got it. - Centralised the paid welcome email enqueue inside `gift-service.redeem()` so new and existing-member redemptions share one code path. - Refactored existing welcome email paths to use the new shared `enqueueWelcomeEmailRun()` method --------- Co-authored-by: Troy Ciesco <tmciesco@gmail.com>
ref #26869 - the allowlist introduced in #26869 protects against executable content on the shared CDN domain, but rejects many legitimate non-executable formats that worked before (camera RAW files, common archive formats, modern image/video containers, 3D/CAD formats, etc.) - the additions are safe because the storage adapter's `getStorageContentType()` already falls back to `application/octet-stream` for any extension whose MIME type is not on the browser-renderable allowlist, forcing a download rather than rendering or executing the file - new extensions cover photography (.np3, .nef, .cr2, .cr3, .arw, .raf, .orf, .rw2, .dng, .tif, .tiff, .bmp, .heic, .heif, .avif), audio (.flac, .aac, .aif, .aiff, .opus, .mid, .midi), video (.webm, .mkv, .avi, .m4v), archives (.7z, .rar, .gz, .tgz, .tar, .bz2), 3D/design (.stl, .obj, .glb, .gltf, .fbx, .blend, .ai, .eps, .xcf), fonts (.ttf, .eot), data/docs (.toml, .tsv, .geojson, .vcf, .numbers, .odp, .ppt, .fb2) - sorted the array alphabetically for easier future maintenance - updated the importer glob test to match the expanded list
ref https://linear.app/ghost/issue/BER-3587/ The onboarding flow was lost during the Ember->React migration of the Admin shell. This recreates it in the React admin app on a dedicated route, while keeping Analytics as the return destination until the checklist is skipped or completed. Because onboarding has been missing for several months, existing owners may have stale onboarding preferences. This PR records `onboarding.startedAt` when `/setup/done` starts the checklist and only shows `started` checklists whose timestamp is on or after the fixed 30 April 2026 cutoff. Missing or older timestamps are dismissed so long-time product users are not suddenly forced into onboarding. ## Summary - adds a dedicated React admin onboarding route at `/setup/onboarding` - stores checklist state through the user preferences hooks while keeping the legacy accessibility storage detail hidden - redirects active onboarding users away from Analytics until they skip or complete the checklist - updates Ember `/setup/done` so new owner setup is the only path that starts onboarding - records `onboarding.startedAt` and dismisses missing/legacy `startedAt` values before the fixed 30 April 2026 cutoff so existing long-time owners are not forced into onboarding - shares the Shade `ShareModal` base between the onboarding publication share dialog and `PostShareModal` - aligns publication and post share options, including X, Threads, Facebook, LinkedIn, and copy link - adds Shade Storybook coverage for the shared share modal and fixes Storybook dark-mode handling for portaled content - adds onboarding E2E coverage, including pending existing owners and legacy started owners reaching Analytics normally
Closes https://linear.app/ghost/issue/BER-3609/open-rate-filter-applies-empty-exact-match-immediately Switches is to is greater than and removes the default value for all email filters.
closes https://linear.app/ghost/issue/BER-3596 - When a gift subscription's validity period ends and the member drops back to free, the admin member-activity feed now renders an "ended paid subscription" entry - Backend reads from the existing `members_status_events` rows (`from_status: gift, to_status: free`), so no new write path or schema change is needed - Copy and icon match the paid-subscription `expired` event so the lifecycle reads consistently to publishers
ref https://linear.app/ghost/issue/BER-3587/ Follow-up to #27625 — the apps/admin build was failing with a TS2353 error because Radix's `DialogContentProps` type does not include an index signature for `data-*` attributes, so passing `data-testid` (or any `data-*` attr) as a literal in `contentProps` failed strict excess-property checks. - widened `ShareModal`'s `contentProps` type to additionally accept arbitrary `data-*` keys, matching how the attribute is consumed on the rendered DOM element - removed the vestigial `data-test-modal` attribute from the onboarding share dialog; nothing references it (the e2e helper uses the `data-testid` selector)
#27681) closes https://linear.app/ghost/issue/BER-3542 Fixes the member activity feed entry for gift members who continue into a paid subscription. When a gift member upgraded to a paid subscription, the subscription event initially had enough context to show `continued paid subscription after gift`. However, `getSubscriptionEvents()` deleted the eager-loaded `paidStatusEvent` relation from the shared `SubscriptionCreatedEvent` Bookshelf model while serializing the first event row. Because multiple `MemberPaidSubscriptionEvent` rows can share the same `SubscriptionCreatedEvent` instance, later rows could lose access to `paidStatusEvent`. That caused `previous_status` to become `null`, and the activity feed could revert to the generic `started paid subscription` wording after the next payment/update event. This change: - keeps `paidStatusEvent` available on the shared relation while deriving `previous_status` - removes `subscriptionCreatedEvent.paidStatusEvent` only from the serialized event payload - adds unit coverage for shared subscription-created-event relations so the regression is caught
ref https://linear.app/tryghost/issue/ONC-1673 Several callers pass either absolute filesystem paths (built via path.join with getContentPath) or paths that already include the storagePath prefix (built via getTargetDir) when calling exists(), save() or delete(). path.posix.join concatenated those onto storagePath verbatim, producing malformed bucket keys that embedded the local filesystem prefix or duplicated the storagePath segment. The exists() probe then targeted a different key than the eventual write, defeating uniqueness checks; on stricter bucket policies the malformed HEAD threw non-NotFound errors, surfacing as the bookmark favicon fallback users have been reporting. Routed all keys through toCanonicalRelativePath, a chain of named handlers (fromAbsoluteFilesystemPath, fromStoragePathPrefixed, fromLeadingSlashPath) that each handle one input shape and return null when their shape doesn't apply, mirroring how LocalStorageBase absorbs the same shapes via _resolveAndValidateStoragePath. Existing path-traversal protection still fires for `..` segments and the change is forward-only — no existing S3 objects move or rename.
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 : )