[pull] main from TryGhost:main - #1450
Merged
Merged
Conversation
…est logs (#30333) no issue - derive a database name only for MySQL acceptance-test workers - derive a SQLite filename only for SQLite acceptance-test workers - stop passing the invalid `filename` option to MySQL2 connections
no ref Shade's stylesheet carried application concerns — most expensively **21 external `fonts.bunny.net` stylesheet imports fetched on every admin page load**, serving only the settings font pickers.
ref 03c62e4 Navigating from an Ember-owned screen such as Posts or Pages to a React-owned screen could leave the old sidebar item selected. React Router uses `pushState` for React-owned routes, which intentionally preserves router history for navigation blockers but does not notify the Ember router. The Ember bridge therefore continued reporting its last route as active.
no ref `@tinybirdco/charts`' `useQuery` is SWR under the hood — a second client cache beside react-query, with SWR's revalidate-on-focus default contradicting the admin's `refetchOnWindowFocus: false`, invisible to devtools and the acceptance harness's teardown. Tinybird queries now ride the framework's react-query layer.
ref https://linear.app/ghost/issue/BER-3897 The redesigned import dialog was reaching publishers only through membersCustomFields, which tied a finished piece of work to an experiment it does not depend on. It now has its own private flag, membersImportRedesign, and the gate chooses between the two implementations on that alone, so the dialog can be released on its own while custom fields stay behind their own switch. Claude-Session: https://claude.ai/code/session_01WHJ1RdhTX8n6KwvrgSEnRT
ref https://linear.app/ghost/issue/BER-3897 With the gate no longer standing in for membersCustomFields, the dialog can no longer assume custom fields exist wherever it renders, and it has to ship ahead of them as a plain mapping of columns onto the member fields Ghost already has. It now asks that flag directly: with it off the definitions are never fetched, no custom field appears as a mapping target, the section that would head them is dropped rather than left as an empty heading, and no row offers to make one — which leaves a fruitless search with nothing force-mounted, so the picker says no field matched instead of going blank. Claude-Session: https://claude.ai/code/session_01WHJ1RdhTX8n6KwvrgSEnRT
ref https://linear.app/ghost/issue/BER-3897 The baseline import dialog carried custom-field mapping that never ran in production, because the gate only rendered that dialog while membersCustomFields was off. Splitting the flags would have made the code live for the first time, so it is gone rather than inherited. The shared auto-detection keeps its custom-field handling, since the redesigned dialog depends on it. With the baseline no longer folding custom fields into the target list, the redesign's own copy of that function became identical to the shared one and is now re-exported instead of duplicated.
ref https://linear.app/ghost/issue/BER-3897 The picker was re-deriving the section order and dropping empty sections itself, duplicating an ordering that field-targets already owned and had tests for. fieldTargets now returns sections that are ordered, headed and non-empty, with the badge and accessible kind resolved onto each target, so the picker renders what it is given instead of working it out. Resolving the badge upstream removed contested from the public shape, since deciding the badge was all it was ever read for, and the tests now assert through that output rather than by importing the presentation table.
ref #30286 `apps/admin-x-framework` failed `test:types` (`tsc --noEmit`): in `use-active-visitors.test.ts`, `renderHook` infers its Props type from `initialProps`, which made `config` non-optional — so the `rerender({config: undefined})` that exercises the empty-`site_uuid` fallback didn't typecheck, and the package's `pnpm test` was red locally.
ref https://linear.app/ghost/project/4b2edbd66469/ - Milestone 6 Each commit references an individual Linear issue Downloads remote media referenced by CSV posts into Ghost storage, with import-level caching and per-row failure isolation. Existing Ghost assets, Unsplash images, and Gravatar images remain untouched.
The allowed-country list, the port definitions and their field destinations are facts about Stripe Checkout rather than about Ghost's server, and Admin needs the same facts to build the checkout config UI without re-deriving them. Extracting them into @tryghost/checkout gives both sides one source, and ghost/core now imports what it previously owned.
The end-to-end suite typechecks Ghost Core's sources, which now reach the shared checkout package, but e2e did not declare that dependency. Nothing ordered the package's build before the e2e typecheck, so it resolved a declaration file that had not been emitted yet and failed on a module it could see the import for. Declaring the dependency puts the build back in the task graph.
The probe only reported whether a request was accepted, which is not the same as whether it does anything: Stripe's form encoding drops an empty object, so a parameter can vanish on the way out and the session is created without it. That read as a working feature and had already produced a wrong claim in this file's own header, that the API treats `allowed_countries` as optional where the spec marks it required. Each accepted probe now prints what the created session will actually ask a buyer for, and the two candidate "everywhere" sentinel shapes are probed alongside it — both are accepted, and both come back collecting nothing.
Storing "everywhere" as a copy of every country Stripe ships to would silently become a restriction the day Stripe adds one, with nothing in the configuration saying so. The list is now optional on the way in and absent on the way out, so the absence itself carries the meaning. An empty list is still refused, because a publisher who cleared it did not mean worldwide. Stripe has no equivalent sentinel — `allowed_countries` is the only key `shipping_address_collection` has, so omitting it sends no parameter and yields a session that collects no address — so the absence is expanded to every country at the one place that builds the session request.
ref https://linear.app/ghost/issue/BER-3884/design-how-publishers-collect-custom-fields-and-shipping-addresses Surfaces that map something to a custom field need a single-select picker with creation in place, starting with tier checkout collection. The CSV import's picker is a different component (multi-source targets, external open/search state), so this one composes the same Shade primitives with the create form inside the popover, where focus handling carries over instead of being fought.
ref https://linear.app/ghost/issue/BER-3884/design-how-publishers-collect-custom-fields-and-shipping-addresses A paid tier's detail modal gains a Checkout card, behind the membersCustomFields flag with Stripe connected: toggles for collecting a shipping address (with allowed countries and destinations for the address and recipient name), a phone number, and a business tax ID, saved through the tier checkout config API as its own sub-resource. The section validates alongside the tier form before either resource is written, reports its dirtiness into the modal's close confirmation, skips its write when untouched, and degrades without touching tier editing when the endpoint is missing (older Core) or the read fails.
ref https://linear.app/ghost/issue/BER-3884/design-how-publishers-collect-custom-fields-and-shipping-addresses The checkout config is a sub-resource keyed by tier id, so the card only showed on saved tiers — a publisher creating a paid tier to collect addresses had to save, close, and reopen before they could configure it, while prices, which ride the tier payload itself, show from the start. The card now shows during creation under the same conditions as prices: its state is held locally, and the one Save creates the tier, takes the id from the create response, and writes the configuration against it. A successful write also rebases the section's dirty baseline directly, since a mount with no tier id has no config for a refetch to rebase.
ref https://linear.app/ghost/issue/BER-3884/design-how-publishers-collect-custom-fields-and-shipping-addresses Four review findings: the wire-contract test's expect-error directive sat on the wrong line and was never actually typechecked before; a malformed config response now degrades the section instead of crashing the modal; the country list moved to its own module so the test asserts against it rather than a literal count; and a spec now proves a saved checkout edit clears the unsaved-changes confirmation.
The tier checkout section wrote "all countries" as a copy of every country Stripe ships to, and read it back by comparing the saved list's length against that set. Both break the moment that set moves: a saved "everywhere" silently becomes a restriction excluding whatever was added. Absence of a list now means everywhere, matching what the API accepts and returns.
ref https://linear.app/ghost/issue/BER-3884/design-how-publishers-collect-custom-fields-and-shipping-addresses Admin kept its own copy of the countries Stripe ships to, a third alongside Ghost Core's and the end-to-end harness's. The Core and harness copies are held together by a test; Admin's was held by nothing, and it is the copy a publisher's saved settings are interpreted against, so drift there offers a country Stripe refuses at session create. It now reads the shared @tryghost/checkout package that Core does. The harness keeps its own copy deliberately, because a fake sharing the product's list could never catch the product offering a country Stripe refuses.
The section restated the server's rule about which custom field type can hold each collected value, once per filter and again per picker, eight times over for three collections. Reading it from the shared port table instead means a destination row names only what is collected, and what may hold it follows.
ref https://linear.app/ghost/issue/BER-3872 A destination can stop being usable between the picker offering it and the save reaching the server, by being archived or retyped to something that cannot hold what is collected. Only the server sees that, and it already names the collection it refused, but the refusal went to a toast that left every picker looking equally fine. It now lands on the picker the server named, carrying the server's own words instead of a guess restated in the client.
Countries on a collecting shipping block are a restriction, so their absence is how a tier says it delivers everywhere. The contract test asserted the opposite at compile time, which would have made that state unexpressible without enumerating a country set that moves. It is now a positive case in both directions, so a client reads the everywhere tier back the way it wrote it.
closes https://linear.app/ghost/issue/NY-1548 The automations browse response can omit stats. Mark the field optional so the backend type reflects that shape.
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 : )