[pull] main from TryGhost:main - #1478
Merged
Merged
Conversation
ref https://linear.app/ghost/issue/BER-3946/update-newsletter-sending-status-copy The pending-send card shown on the Overview and Newsletter tabs while an email is going out read "This newsletter is still sending". The "still" gives the message a negative tone for what is a normal in-progress state. Replaced it with "Your newsletter is being sent", which matches the tone of the sending banner and the post-publish success modal.
closes [NY-1537](https://linear.app/ghost/issue/NY-1537/) closes [NY-1535](https://linear.app/ghost/issue/NY-1535/) ## Context Automation emails were switched to the bulk Mailgun domain in [#29728](<#29728>). As part of that work, responsibility for applying `bulkEmail:mailgun:tag` (ex: `blog-{site_id}`) moved from the shared Mailgun client to individual callers. Newsletter sends continued applying the configured site tag, but automation sends bypassed that provider and initially sent only the `automation-email` classification. Automation analytics had originally been introduced in [#29333](<#29333>) with a filter containing only `automation-email`. [#30093](<#30093>) restored the configured site tag on outgoing automation emails. Automation analytics intentionally continued querying only `automation-email` during the rollout window so events from previously sent emails would not be excluded. That rollout window has now passed. This change completes the transition by querying automation events using both `automation-email` and the configured site tag, preventing sites on shared Mailgun domains from fetching one another's automation events. ### Relevant commits * [`58244a1`](<58244a1>) switched automation emails to the bulk Mailgun domain and moved configured tag handling out of the shared Mailgun client. * [`f84ed756`](<f84ed75>) introduced automation email analytics using the `automation-email` filter. * [`d64be44f`](<d64be44>) restored the configured site tag on outgoing automation emails while leaving analytics filtering unchanged for the transition period. ## Testing Manually tested, and confirmed that automation email analytics jobs include the `bulkEmail:mailgun:tag` in the query (via a temporary console log), and confirmed that email opens still populate for automations.
no ref This change should have no user impact.
ref https://linear.app/ghost/issue/BER-3864/differentiate-public-and-private-member-projections Which of the extra fields a publisher defines about their members come back is about to depend on which side of Ghost is asking, and several callers said nothing and inherited staff as the answer. That is the wrong thing to inherit: a surface that silently loses these fields sends an incomplete payload, which someone notices, while the other default hands a reader fields that were never theirs, which nobody does. Fetching them is now something a caller asks for, and the surfaces that show them say whose view they are showing. Two callers had been quietly relying on the old default. The unsubscribe link, which identifies someone by a signed link rather than a session, was loading every value a member holds onto the request and then discarding all of it behind a fixed list of newsletter preferences. The CSV import and export declared their own narrower view of the fields service with the argument missing altogether, which TypeScript accepts, so it had been arriving as nothing at all.
ref https://linear.app/ghost/issue/BER-3864/differentiate-public-and-private-member-projections A publisher collecting a delivery address and an internal note about the same member wants different answers to who may see each, so which of the extra fields they define are open to the member whose record it is now belongs to the field rather than to the feature. A field can be closed, readable, or readable and writable, and it is closed when it is made, including every field that already exists. Nothing infers the setting from a field's name, type or use, and no client that has never heard of it can open one, because the permissive answer here publishes what a site has already collected to the people it was collected about. A field closed to members behaves, to that member, exactly as a field nobody has defined: absent from what they are offered, absent from their own record, and a write naming it refused in the same words a write naming nothing gets. That is not two code paths written to agree. The narrowing happens in the query, so a closed field is never fetched and "unknown field" is the only answer left to give. Fetching everything and dropping what the audience may not have leaves a correct answer one forgotten filter away, and the way that fails is silent. It is also why there is no longer a way to ask for the site's fields without saying who is asking. The filter is unconditional, staff included. They are narrowed to a list that happens to hold every level rather than routed around the clause, so there is no branch to reach by accident and an audience nobody has taught this about narrows to nothing at all. Getting it wrong costs a publisher sight of their own fields, which somebody reports within the hour. The other direction is the one nobody sees. A member is refused differently when they may read a field but not change it, because they are looking at it and telling them it does not exist would be a lie about something on their screen. A site whose every field is closed carries no such key in a member's payload at all, rather than an empty one: an empty bag says a publisher collects something about them without saying what. An access change is recorded in the history with the level it became and the level it was, unlike the other edits, because who could read a member's answers, and from when, gets asked long after the current setting has stopped being the answer.
ref https://linear.app/ghost/issue/BER-3864/differentiate-public-and-private-member-projections Deciding who a field is for now happens where a publisher makes the field, rather than against the API by hand. The choice is one of three rather than two switches, because the levels are ordered: a member cannot usefully change something they are not shown. Opening a field says, on the spot, that anything already recorded in it becomes visible to the member, which is the part nobody would expect. It reads as a change of display and is a disclosure of everything staff have written there since the field existed. Who each field is for also reads off the list itself, because which of them members can reach is a question asked of the whole list rather than of one field at a time. Admin ships separately from Core, and a Core that predates the setting sends no answer at all; that reads as staff-only, in one place, since guessing the other way would draw a field as open on a Ghost that has no such idea and the publisher would believe it.
ref https://linear.app/ghost/issue/BER-3864/differentiate-public-and-private-member-projections A publisher sets this in Settings and a member lives with the result on the other side of Ghost, through a different API, signed in as themselves. Until now nothing watched both ends at once: the Admin tests drive the control against a faked server, and the API tests prove the rules with no publisher and no browser, so a control that saved the setting somewhere the enforcement never read would leave both green. This defines a field through Settings, fills it in on a member, signs a real member in through the magic link and asks what they are told, then opens and closes the field and asks again. It reads the member's own side over its API rather than through Portal, which does not render these yet. Two of the Admin tests were the weaker half of what this now proves and have gone; what stays there is the disclosure warning, whose condition is the app's own, and the older backend that sends no setting at all, which no real Ghost can be made to serve. Also removes a self-referencing type on the definitions query. The type is read off the function's own signature, so naming it as the return type made it circular, which the main typecheck accepted as an implicit any and the E2E workspace's stricter one refused.
ref https://linear.app/ghost/issue/BER-3864/differentiate-public-and-private-member-projections A test had half as long to finish on a laptop as it had on CI, so a correct test could fail in front of the person writing it and pass once pushed. That is the worst way for a suite to be wrong: it costs an investigation every time, and it teaches everyone to disbelieve a local failure, which is the one signal that arrives early enough to be cheap. Half a minute is also not the difference between a fast iteration loop and a slow one, so the shorter budget was buying nothing to offset that. It was not a decision anybody made. The two were deliberately brought together at thirty seconds once; a later commit raised CI to sixty because CI needed longer, and splitting them again was a side effect of that rather than its intent. The identical branches left behind on the assertion timeout are the fossil of the same tidy-up, and go here too.
no ref Consolidated the "add Tag" picker in the Posts/Pages list with the Editor sidebar tag picker.
Custom member fields are intended for one Ghost(Pro) plan and above, but nothing could express that. A labs flag says a feature does not exist yet, which is the wrong thing to tell someone whose plan simply does not include it, and it is the same answer for every site. This adds limitCustomFields alongside the other flag limits the host already sets, so the decision about which plan carries the feature is configuration on the hosting side rather than a change to Ghost. It stays open unless a host switches it off, so nothing changes for anyone until that configuration exists. The routes that exist only to change field definitions sit behind one guard, and Admin asks the same question before offering any of it, so a publisher whose plan excludes the feature is told so rather than shown a door that refuses them.
ref https://linear.app/ghost/issue/BER-3951/ Gift delivery analytics queried all events on shared Mailgun domains because the filter only included `gift-delivery`. Gift delivery sends now include `bulkEmail:mailgun:tag`, and analytics require both tags. Missing or empty site tags remain supported. Automation analytics were already fixed in [#30430](#30430); this change completes site scoping for gift delivery emails.
…0628) no ref The post editor's settings sidebar has two save gates, and they were not validating the same thing. Both gates now read `validatedFieldsOf(live)`, so the key list is the only thing deciding what either of them checks.
…st editor (#30629) no ref The Facebook and X card panes had each grown their own copy of the accepted image types, the unsupported type message, the upload error mapping, the fallback chain for a card's title, description and image, and the `FieldError` element the meta-data pane also carries.
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 : )