Skip to content

[pull] main from TryGhost:main - #1458

Merged
pull[bot] merged 11 commits into
code:mainfrom
TryGhost:main
Aug 31, 2026
Merged

[pull] main from TryGhost:main#1458
pull[bot] merged 11 commits into
code:mainfrom
TryGhost:main

Conversation

@pull

@pull pull Bot commented Aug 31, 2026

Copy link
Copy Markdown

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 : )

peterzimon and others added 11 commits August 31, 2026 11:48
refs
https://linear.app/ghost/issue/DES-1482
https://linear.app/ghost/issue/DES-1483
https://linear.app/ghost/issue/DES-1484
https://linear.app/ghost/issue/DES-1485

Admin 7.0 → Page chrome, delivered incrementally behind a private flag.

---------

Co-authored-by: Steve Larson <9larsons@gmail.com>
Subscription attribution URLs come from public signup and checkout URL
history, which the server does not scheme-validate, so a member can end up
with a javascript: URL stored as their own attribution. The member detail
view rendered it straight into an anchor, unlike the sidebar and activity
feed, which already pass attribution URLs through the isSafeHref guard.
Current browsers refuse to run a javascript: URL opened in a new tab, which
is how this link opens, so it was not exploitable in practice, but the page
URL now takes the same guard for consistency: unsafe URLs render as text
while site-relative and http(s) URLs stay links.
closes https://linear.app/ghost/issue/NY-1549

This is a dev-only change.

Tinybird data is now persisted across restarts. This is adapted from
[Tinybird Local's docs][0].

Manual test
-----------

I did the following twice (after running `pnpm docker:clean`):

1. `pnpm dev:analytics`
1. `pnpm data:analytics:generate 100`
1. Verify that analytics appear in the UI
1. Stop the server started in Step 1
1. `pnpm dev:analytics` (again)
1. Look at the analytics in the UI

Before this change, the analytics disappeared in the final step.

After this change, the analytics persist.

[0]:
https://www.tinybird.co/docs/forward/core-concepts/tinybird-local#run-with-docker-compose
no ref
- tinybird local is a huge docker image, building a distilled one allows
us to save on runner disk space
ref https://linear.app/ghost/issue/BER-3872

A Stripe checkout can be started with nothing but an email address, and typing
an email is not proof of owning it. The webhook wrote whatever the checkout
collected onto whichever member the address resolved to, so anyone could
overwrite a real member's stored answers by checking out with that member's
email. The write is now gated: values still land on the member the webhook
itself just created, since that record holds nothing the buyer didn't supply,
but a pre-existing member is only written when the session was started by a
signed-in member.
no ref
- skip lockfile trust verification in jobs downstream of setup
- cache lockfile verification file to speed up verification
- reduce duplicate steps across jobs with standardized composite action
no ref
- increase max worker count when possible in github action runners
- speed up theme editor codemirror tests
ref BER-3869

A filterable field carried one hand-written translator that decided three things at
once: where its value sits in the query, what the comparison means, and how that
gets written down. The three varied independently but had to be re-implemented
together, so "what contains means" was written out again for every field offering
it, and any drift between the writing and reading halves went unnoticed until a
value came back wrong. A field now names an addressing and a vocabulary, and the
pairing is composed rather than restated.

Declaring a field also carried only part of what a field is. Its icon lived in a
switch in the picker, its place in the menu was an append somewhere else, and its
operator wording came from a map shared by every field whatever its type. All of
that moved into the declaration, where the compiler can check it: an operator a
field's vocabulary cannot write is rejected where it is written rather than dropped
later, an icon nobody drew fails the build, and a field nobody placed in a group
fails naming the field.

Three rules the engine keeps rather than asks anyone to remember. A key is a path,
so a question about it is asked in steps and `newsletters.slug` is not a prefix of
`newsletters.slugfoo`. Whether a filter names a field is answered by parsing it,
because only the parser knows which characters are structure and which are somebody's
data. And a filter is only read once the site's own definitions have arrived, since
reading one early answers a wider question than was asked and then saves that answer
over what the publisher wrote.
ref BER-3869

The unit tests prove a predicate survives being written and read back, but not that
the filter a publisher builds in the picker is the one the members list answers.
These drive the real UI: define a custom field, filter on it, save the filter as a
segment, reopen it, and check the pill and the results both come back saying what
was asked. That is the trip a saved segment actually makes, and nothing below this
level covers it.
ref BER-3869

Subscribing to a newsletter is written as being on the list and not having bounced,
joined by and; unsubscribing is the denial of exactly that, joined by or. Reading one
only looked at whether the list was named and negated, and took any arrangement of
the remaining parts to mean the same thing. Being on the list or having bounced is a
wider set of members than being on it and not having bounced, so a filter written
that way was answered as though it said something narrower, and saving the page then
wrote that narrower version over it.

The pair is now recognised only as one of the two shapes that mean it, which is what
the subscription vocabulary next door already required of the same three parts.
Anything else is left unread, the way any clause this build does not recognise is.
no ref

All I did was run this:

```sh
npx skills add tinybirdco/tinybird-agent-skills
```
@pull pull Bot locked and limited conversation to collaborators Aug 31, 2026
@pull pull Bot added the ⤵️ pull label Aug 31, 2026
@pull
pull Bot merged commit de06cef into code:main Aug 31, 2026
3 checks passed
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants