[pull] main from TryGhost:main - #1530
Merged
Merged
Conversation
closes https://linear.app/ghost/issue/BER-3932/make-email-analytics-event-result-merging-linear-time Merging each event's result rebuilt the accumulated email and member ID lists from scratch, so ingestion slowed quadratically as a batch grew towards the 5000-member mid-fetch aggregation threshold. Persistent membership sets now let merge() check only the incoming IDs while keeping the same lists in the same first-seen order. The service's running result now tracks event counts only. It exists because each mid-fetch aggregation resets the processing result, and the job still needs totals for the whole run. It used to copy every page's new IDs as well, which meant diffing against all IDs seen so far on each page and holding them until the job finished, even though only the counts are ever reported.
Refines the React editor’s publish flow to match the preview controls and make recipient selection, scheduling, and final confirmation easier to use. Addresses all 11 items in [PLA-461](https://linear.app/ghost/issue/PLA-461/publish-flow-refinements), each in its own commit. - Aligns fullscreen actions, updates spacing and typography, and removes animation when switching between Preview and Publish while retaining editor entry transitions. Closing Preview dismisses the publish flow too and returns to the editor; switching back with Publish preserves its choices. - Adds animated, indented options; reuses the searchable member label picker without editing controls, grouped into active tiers, archived tiers, and labels; places compact scheduling inputs in a bottom-aligned second column so the timing radios stay in place. - Keeps empty and single-row token fields at the standard input height with equal padding, and restores the label-picker chevron in publish recipients and member details. - Places the green publish action on the right with white text, alongside an outlined Back button. - Applies the same content width, typography, spacing, Close positioning, and action alignment to the unschedule and unpublish modals. - Restores Ember’s completion navigation: pages return to the page list, scheduled posts and posts without email return to the post list, and immediate posts with email open post analytics. Includes previously emailed posts and email-only sends, and hands navigation to Ember when it owns the destination list. - Matches completion-screen headings, spacing, card typography, and actions to the other publish steps. - Addresses review findings around keyboard access, heading line height, mobile overflow, transition state reset, and recipient row height while newsletter counts refresh. Validation: 158 focused browser tests passed across publish, preview, editor header, scheduled dates, member labels, and bulk actions; 271 focused unit tests and Admin typecheck passed. Repository-wide formatting, lint, dependency boundaries, package checks, and documentation checks passed. Full `pnpm check` failed in untouched test files: 30 Ghost Core test timeouts, three Admin test/hook timeouts, and one Admin fixture suite unable to resolve the Koenig build output (`koenig-lexical/dist/koenig-lexical.js`). Visually checked desktop and 390px mobile layouts; the mobile dialog has no horizontal overflow. The latest layout follow-ups passed all 56 publish-flow browser tests, including regressions for timing radio movement, recipient section height during newsletter count loading, and single half-turn chevrons alongside legacy Admin CSS. The Preview dismissal follow-up passed all 87 editor-header and preview browser tests, including Close, Escape, the preview shortcut, focus restoration, and reopening. The unschedule/unpublish styling passed all 12 update-flow browser tests and was visually checked at desktop and 390px mobile widths. The completion-navigation follow-up passed all 92 editor-header and preview browser tests plus Admin typecheck, with regression coverage for posts, pages, schedules, email-only sends, and previously emailed posts. The recipient-grouping follow-up passed 68 publish/member-label browser tests and Admin typecheck, with the dropdown visually checked locally. The no-flash follow-up passed 107 publish-flow/editor-header browser tests and Admin typecheck. Completion CI follow-ups passed 51 editor-header and 9 newsletter-retry acceptance tests, Admin typecheck, and E2E typecheck. All 12 journeys across the three affected E2E files passed against the local test backend across the main run and a focused rerun; one unrelated settings-sidebar timeout passed on rerun. A subsequent CI scheduling failure exposed a race with the destination Ember success modal; the test now explicitly waits for and closes that modal before reopening the post. The focused scheduling journey and E2E workspace lint/type checks passed after this correction. These journeys cover scheduling and unscheduling through the returned Ember list, publish-only navigation, newsletter delivery and analytics navigation, access rules, and post settings. Component and acceptance tests use the fake Admin API; E2E tests use isolated backend fixtures. The token-field and chevron follow-up passed all 284 Shade tests, Shade lint/build, 65 publish/member-label browser tests, and Admin typecheck. Visually verified standard/empty/selected fields at 32px in Storybook, plus the chevron on publish and member forms. A subsequent local Admin asset-copy step hit an ActivityPub dist symlink collision after compilation; the clean Admin build passed in CI on commit `9ec27614c8`. - [x] Read and followed the Contributor Guide - [x] Explained the change - [x] Added automated regression coverage
closes https://linear.app/ghost/issue/NY-1582 closes https://linear.app/ghost/issue/NY-1584 Introduces a combined performance-stats endpoint for all-time entry history and in-progress, completed, and exited-early counts. Derives chart and status totals from one Tinybird query so they reconcile. Updates Tinybird sorting keys to support scoped queries while preserving existing data and latest-version deduplication. Retains the automation list's MySQL fallback. Commits progress through storage, query, history formatting, response validation, and API wiring. Tests cover: - Status precedence, deduplication, and site/automation isolation. - Complete history, date boundaries, empty data, and matching totals. - Staff authentication, permissions, invalid responses, and failure paths. _Best to review this commit-by-commit._
…31026) No ref Replaced the post analytics sending banner with a single status line under the post title, and gave posts list rows the same line. All wording is unchanged from main. <img width="1280" height="720" alt="image" src="https://github.com/user-attachments/assets/3b2829bf-0740-436b-96ca-8022811f290d" /> <img width="1280" height="720" alt="image" src="https://github.com/user-attachments/assets/22c2b0c6-3b1d-44d7-9f06-cca9e4c737f1" /> --------- Co-authored-by: Kevin Ansfield <kevin@lookingsideways.co.uk>
no ref Embed previews render in a separate-origin renderer, and when it fails to answer the editor only showed a placeholder. Nothing was reported, so there was no way to tell a blocked frame from a wrong document or a version mismatch. The card now passes each failure to Koenig's `onError`, which the post editors already send to Sentry. `onError` was typed on the composer context but never provided, so it is now passed through. Card render crashes caught by Koenig's error boundary reach the host's handler as a result.
…31110) no ref The admin acceptance suite flaked on CI in [this run](https://github.com/TryGhost/Ghost/actions/runs/36593525443/job/109493906527) on #31093: `Posts list rows › marks a featured post` failed with a 418 for `POST /stats/posts-member-counts/`, even though that endpoint was faked for the test. The request had been issued just before the test ended, but the harness only learned of it once the service worker relayed it. By then the drain had finished and the fakes had been reset. Slowing the MSW service-worker relay (a random 0–150ms delay before it hands each request to the page) turned that one flake into about 80 failures across the suite. It also exposed specs that only passed because the in-browser fake API answers almost instantly. Working through those showed three kinds of problem: the hole in the harness behind the CI flake, specs that assumed an ordering the app doesn't promise, and two real UI races.
closes https://linear.app/ghost/issue/NY-1621 This change should have no impact on functionality. Now that the server always sends an automation description, let's remove the fallback. In addition to automated tests, I also manually verified that this works as before.
refs https://linear.app/ghost/issue/NY-1626/ ## Problem MailgunClient rejects failed sends with `{ error, messageData }`. Automation polling logs that wrapper as `err`, but Ghost's logger expects the error itself. The resulting `automations.poll.step_execution_failed` event ends in `undefined` and loses the original message and stack, making failed sends difficult to diagnose. ## Solution Unwrap a nested Error in the step failure handler before logging it. Preserve the original error object, event name, and step ID without adding the email payload to the log. Co-authored-by: Evan Hahn <evan@ghost.org>
closes [NY-1642](https://linear.app/ghost/issue/NY-1642) A simple increase to the limit.
…UI (#30856) ref https://linear.app/ghost/issue/GVA-1001 When the selfServeArchives flag is on, the export tab only showed the "Export data" button, so the quick post analytics CSV download was only reachable by opening the export dialog and downloading a full zip. Post analytics serve a bit of a different purpose, with publishers often exporting these on a regular basis (compared to exporting for the purpose of a backup or change of hosting provider). The button is shared between the flagged and unflagged layouts so the two can't drift apart while the flag is still in place.
…hanges (#31094) no ref A background read of the post could hand the React editor another writer's version token without their content. This tab's next save then passed Core's collision check and silently overwrote their changes.
closes https://linear.app/ghost/issue/NY-1618 *I recommend reviewing with whitespace changes disabled.* What ---- This change should have no user impact. Before this change, automations were run off their `slug`. Now, they're run off their `trigger_tier_scope`. (This will get more complicated when we add new trigger types, but this is good enough for now and much more easily extensible.) Why --- In the near future, publishers will be able to create their own automations. We don't want to rely on hard-coded slugs for this. Test plan --------- In addition to manual tests, I also manually ensured automations still ran (e.g., received an email) after this change.
no ref Embed cards kept the oembed provider's `thumbnail_url`, so newsletter video previews hotlinked third-party images and broke whenever the provider stopped serving them. Imported posts already had their embed thumbnails self-hosted, but newly inserted embeds did not. Also switched YouTube thumbnail fetching to their larger, higher-quality images without borders. - The oEmbed endpoint now stores the thumbnail via image storage and returns the stored URL as `thumbnail_url`, keeping the provider's URL in a new `thumbnail_url_original` property - YouTube's `hqdefault.jpg` letterboxes 16:9 videos with black borders, so the 1280x720 `maxresdefault.jpg` is stored instead and the thumbnail dimensions are updated to keep email previews at the right aspect ratio. Videos without a max resolution thumbnail (YouTube 404s for older low-res uploads and some Shorts) fall back to the provider's thumbnail - If the image can't be stored, the provider's URL is kept so the card still has a thumbnail - Mentions are skipped because they aren't stored in content and can be triggered by third parties sending webmentions - The embed node now URL-transforms `metadata.thumbnail_url`, so self-hosted thumbnails (relative or absolute site URLs) are stored with the `__GHOST_URL__` placeholder. Otherwise the relative URLs returned by local storage would reach emails unresolved
SSO adapters currently receive only the owner's ID and email, so an owner-mapping adapter cannot distinguish an active seeded owner from Ghost's inactive first-run placeholder. Expose the existing user status through both SSO repository lookups and an optional field on the base interface. Existing production adapters keep their current behavior. This supports Ghost-Moya's preview-only IAP adapter: authorized reviewers share the active preview owner, without changing its email or reseeding the database. Empty previews do not activate that adapter and retain owner setup and password login. Validation: - SSO base package tests and type checks passed. - Ghost session-from-token unit test passed. - Database-backed SSO API tests passed for verified login, inactive-owner rejection/setup availability, and SSO-disabled behavior. - Changed-file lint, format, dependency-boundary, Markdown, and secret checks passed; Ghost TypeScript build passed. - `pnpm check` passed formatting/lint but failed on unrelated gift-preview image timeouts and Koenig browser tests requiring an unavailable Firefox binary. Deployment: merge before enabling seeded preview SSO in Moya, then deploy a Ghost PR image containing this commit or a descendant. No migration or database rewrite is needed. A patch changeset covers the SSO base package.
closes https://linear.app/ghost/issue/NY-1468 This is a test-only change that should help keep migrations and `schema.js` in sync.
no ref The embed renderer works out an iframe embed's height from its own width. Where scrollbars take up space, a frame a few pixels too short shows one, which narrows the renderer and shrinks the height, which removes the scrollbar and grows it again. Each change triggered another report, so the renderer never yielded and flooded the editor with resize messages. The editor sizes the frame to fit, so the renderer never needs to scroll. Hiding its overflow keeps the width, and so the height, stable. The message protocol is unchanged, so this ships as the same renderer version.
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 : )