Skip to content

[pull] main from TryGhost:main - #1468

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

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

Conversation

@pull

@pull pull Bot commented Sep 3, 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 : )

tommywong-theinitium and others added 11 commits September 3, 2026 13:12
no ref

Added Chinese translation in new fields.
Two races made these specs unreliable. A screen's unsaved-changes guard is not
armed the moment you type: the keystroke has to reach the screen's dirty state,
re-render the guard and register it with the router, and none of that has
happened by the time the typing step resolves. And the route assertion the
specs used as a cue to act reads the address bar, which the browser updates
before the router re-renders, so a spec that reopens a dialog it just closed
could type into the copy already on its way out and lose the edit. They now
wait on the app instead: on the guard itself, by dispatching a synthetic
close-the-tab event and seeing whether the app objects, and on the departing
dialog actually being gone before acting on its replacement. The fake API the
suite runs against also shut itself down on that event, so it now ignores it.

Claude-Session: https://claude.ai/code/session_01HjrKZqSXQ54LgAsX1VMzQp
no ref

Adds some e2e tests for the post editor to cover React & Ember paths. Most of our coverage is only in Acceptance.
ref https://linear.app/ghost/issue/DES-1519/

## Why

The Admin 7 display-oriented Inter features are intentionally limited to
headings, but three prominent title treatments are not all represented
by semantic heading elements. They should use the same title typography
while the surrounding UI keeps the quieter base feature set.

## What changed

- Applied the existing heading Inter features to the sidebar site title.
- Applied them to the post title field in the editor.
- Applied them to publish-flow titles, including “Ready, set, publish.
Share it with the world.”
- Kept the change inside the existing `admin7PageChrome` shell and
portal scope, so flag-off presentation is unchanged.
- Added no automated tests because this is a CSS-only font-feature
adjustment with no behavioral contract; visual acceptance will be done
manually before merge.
ref https://linear.app/ghost/issue/BER-3920/

Adds `GET /ghost/api/admin/emails/:id/status` so Admin can poll
newsletter email preparation and provider submission progress
independently from the email resource.

The response exposes:

- sending status: `preparing`, `submitting`, `submitted`, or `failed`
- completed and total recipient counts
- a rough estimated number of seconds remaining
- the phase in which a failed send stopped
ref https://linear.app/ghost/issue/BER-3922/rename-the-code-behind-the-metafields-api-to-match-the-domain-language

The API vocabulary, the CSV columns and the filter relation all say
"metafields", but the package holding the shared catalog and its column
predicate still said "custom field", so the same concept went by two names
depending on which layer you were reading. Renaming the package to
@tryghost/metafield-types, and isCustomFieldColumn to isMetafieldColumn,
leaves one word for it everywhere the shared vocabulary is used.

Claude-Session: https://claude.ai/code/session_01XFCbqgYHYhd9rZ5WXtqZyT
ref https://linear.app/ghost/issue/BER-3922/rename-the-code-behind-the-metafields-api-to-match-the-domain-language

Members carry a bag of extra fields grouped by the namespace that owns them, and
the API, the CSV columns and the filter relation all call that mechanism
metafields. The tables underneath still called it custom fields, which reads as
though the publisher's namespace were the only one there can be. The definitions
table, the values it keys, the bindings a checkout writes through, and the column
naming that key now use the same word as everything above them. They are dropped
and recreated rather than renamed in place, because the feature sits behind a
private flag with no released data, so there is nothing to carry across and
rebuilding gives every foreign key and index its new name without a per-dialect
rename dance. products_checkout_fields keeps its own name and is rebuilt only
because its foreign key points at the bindings table.

One sealed migration had to change with them. The leaf-rows migration in 6.57
asked schema.js for the shape of the column it adds, and the idempotency suite
re-runs every migration against the final schema, where that table no longer
answers to its old name. Pinning the shape inline, the way the same migration's
rollback already does, makes it self-contained and leaves what it does untouched.

The tier checkout API still speaks of a custom_field_key on the wire. Renaming a
published field is an API change rather than a schema one, so it is deliberately
left where it is.

Claude-Session: https://claude.ai/code/session_01XFCbqgYHYhd9rZ5WXtqZyT
ref https://linear.app/ghost/issue/BER-3922/rename-the-code-behind-the-metafields-api-to-match-the-domain-language

A member carries a bag of extra fields grouped by the namespace that owns them,
and metafields is the name for that mechanism everywhere it is already visible:
the payload key, the route, the CSV columns, the filter relation. Behind them
the service still called the whole mechanism custom fields, which reads as a
claim that the publisher's namespace is the only namespace there can be. The
service and its three aggregates, the types they hand out, the config ceiling,
the filter relation, the telemetry event names, and the names the members read
and write path, the importer, the exporter and the Stripe checkout binding know
their collaborators by, all now use the same word as the API above them.

Several things keep the old word because they are about the publisher's
namespace rather than the mechanism, and are better left accurate than made
uniform. The labs flag gates a feature the publisher knows as Custom fields. So
does the error copy, the limit error code and the stored name of an edit in the
activity log, all of which are read by someone looking at that screen; renaming
them would also split a string across the boundary where admin and core deploy
independently, in exchange for nothing. The tier checkout API names a key that
has the custom namespace already applied to it, so a custom field key is exactly
what it is. Stripe's own checkout custom fields are a different concept that
happens to share a name.

The permission object type is still member_custom_field. Renaming it means
rewriting a fixture and migrating the rows that already carry it, which is its
own change rather than a line in this one.

Claude-Session: https://claude.ai/code/session_01XFCbqgYHYhd9rZ5WXtqZyT
ref https://linear.app/ghost/issue/BER-3922/rename-the-code-behind-the-metafields-api-to-match-the-domain-language

Reading a field definition is no longer gated by a permission. A definition says
only that the site collects a shoe size, and every signed-in member is already
shown the whole list through Portal, so holding it back from staff protected
nothing that was not already on offer elsewhere.

It was also inverted. An integration holding member browse received members'
field values on the member payload, which never consults this permission, while
a request for the definitions describing those same values was refused. It could
read the data but not the schema, which is the wrong way round. The routes had
already drawn the line correctly and deliberately: reads are unflagged so Admin
can ask any site and get an empty list, and everything that changes something is
flagged. Only the permission layer had not caught up. What remains on the
resource is defining fields, which stays with the publisher.

The smaller change alongside it is one of order. Which namespace is being
written to now settles before who is asking, so a namespace nobody can define in
refuses on that ground whatever role the caller holds, rather than answering
with a missing permission that no one could have been granted. That is also
where namespace ownership belongs once an app owns a namespace of its own, since
the caller will then have to be that app rather than hold a role.

Claude-Session: https://claude.ai/code/session_01XFCbqgYHYhd9rZ5WXtqZyT
no ref

Added raw components for the post preview activity in the React editor implementation. Nothing is wired up yet.
ref https://linear.app/ghost/issue/BER-3920/

Allowed unreleased Admin features to be tested on live sites without changing
server-side Labs settings. Overrides are read from the labs query parameter,
persisted for the browser session, and synchronized with Ember during cold
loads so both Admin implementations use the same flag state.
@pull pull Bot locked and limited conversation to collaborators Sep 3, 2026
@pull pull Bot added the ⤵️ pull label Sep 3, 2026
@pull
pull Bot merged commit b16f895 into code:main Sep 3, 2026
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.

5 participants