Skip to content

[pull] main from TryGhost:main#1136

Merged
pull[bot] merged 13 commits into
code:mainfrom
TryGhost:main
May 13, 2026
Merged

[pull] main from TryGhost:main#1136
pull[bot] merged 13 commits into
code:mainfrom
TryGhost:main

Conversation

@pull
Copy link
Copy Markdown

@pull pull Bot commented May 13, 2026

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

github-actions Bot and others added 13 commits May 13, 2026 10:20
closes https://linear.app/ghost/issue/BER-3556/
ref https://linear.app/ghost/issue/BER-3584/

## Summary
- Added the `commentsThreads` private Labs flag and Admin private feature toggle
- Added comments-ui threaded display behind the `commentsThreads` flag
- Supports nested reply display with infinite depth
- Hides the "Replied to" context line when threaded display is active

---------

Co-authored-by: Weyland Swart <weyland.swart@gmail.com>
…27858)

closes https://linear.app/tryghost/issue/HKG-1699

- Redirect storage is moving away from local disk, and the next step needs
confidence that the GCS-backed implementation behaves correctly against a
real object-storage-like service.

- Using MinIO here gives us that coverage locally and in CI without hitting
real GCS, while still exercising the storage behaviours that mocks are
likely to miss, like bucket setup, auth, endpoint config, and path-style
requests.

- The redirects bucket is separate from the media bucket so this test setup
matches the storage boundary we expect in production, without changing
the existing media upload flow.
ref https://linear.app/ghost/issue/BER-3529

When the gift subscriptions feature shipped, copy for the buyer purchase
confirmation email and the recipient expiry reminder email was still
under review, so the strings were intentionally left as raw English
literals.

Copy has now been reviewed for the most part, so this PR wraps member
emails into translatable strings.
ref #27503
ref https://linear.app/ghost/issue/BER-3554

The defensive fallback `gift = 0` was added in
[#27503](#27503) as a temporary
safety net as Admin is deployed ahead of the server on Ghost (Pro). Now
that the corresponding server change (that includes `gift` in the member
count) has been rolled out fully, we can remove the defensive fallback.
ref 986f78e

Let's test this middleware without stubbing.
no ref

`Check app version bump` fails any PR that changes a file under
`apps/{portal,sodo-search,comments-ui,announcement-bar,signup-form}`
without bumping that app's `version` field. Renovate never bumps app
versions when it updates dependencies, so every dep bump that touches
one of those apps' `package.json` files dies on this check — including
current security PRs (postcss, vite, others).

This change exempts diffs whose only change inside a monitored app is
`package.json`. A human PR that edits both source and `package.json` in
the same app still trips the check, so the cache-busting guarantee for
actual code changes is preserved.

## Test plan

- [ ] Trigger the workflow on this PR — script touches no monitored app
files, so the check should report no app changes detected.
- [ ] Rebase one of the stuck Renovate security PRs (e.g. #27595,
#27354) onto this branch and confirm `Check app version bump` passes.
- [ ] Open a synthetic test PR that edits a source file under
`apps/portal/src` without bumping `apps/portal/package.json` and confirm
the check still fails.
## Summary

- Removed the unreachable `protectBruteForce` stub in
`services/auth/passwordreset.js`. It read a `tokenSecurity[key].count`
value that was never assigned anywhere in the file, so the `count >= 10`
guard was always false and the in-file rate limit never triggered.
- Removed the matching `tokenSecurity` constant, the `tokenLocked`
message, the `// @TODO` comment pointing at #7579 (2016), and the
now-unused `protectBruteForce` export.
- Dropped the awaited call from `api/endpoints/authentication.js` since
the function only forwarded `{options, tokenParts}` unchanged on the
success path.

## Why

The actual brute-force protection for the password reset flow already
lives at the route layer in `web/api/endpoints/admin/routes.js`:

- `PUT /authentication/password_reset` is wrapped by
`shared.middleware.brute.globalBlock` (express-brute, DB-persisted in
the `brute` table, 50 attempts/IP/hour by default — configurable via
`spam.global_block`).
- `POST /authentication/password_reset` adds `globalReset` (per-IP) and
`userReset` (per-username).

The TODO predated that middleware being wired up to this route, which is
why the in-file counter was never finished — the protection landed at
the route layer instead. The stub had no tests covering it (`grep`
across `ghost/core/test` returns nothing), wasn't called from any other
consumer, and recently caused a false-positive in a security report.
Deleting it removes a misleading code path without changing behavior.
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [@dnd-kit/utilities](https://redirect.github.com/clauderic/dnd-kit)
([source](https://redirect.github.com/clauderic/dnd-kit/tree/HEAD/packages/utilities))
| devDependencies | pin | [`^3.2.2` →
`3.2.2`](https://renovatebot.com/diffs/npm/@dnd-kit%2futilities/3.2.2/3.2.2)
|
|
[@testing-library/jest-dom](https://redirect.github.com/testing-library/jest-dom)
| devDependencies | pin | [`^6` →
`6.9.1`](https://renovatebot.com/diffs/npm/@testing-library%2fjest-dom/6.9.1/6.9.1)
|
|
[@testing-library/jest-dom](https://redirect.github.com/testing-library/jest-dom)
| devDependencies | pin | [`^6.9.1` →
`6.9.1`](https://renovatebot.com/diffs/npm/@testing-library%2fjest-dom/6.9.1/6.9.1)
|
| [@vitest/coverage-v8](https://vitest.dev/guide/coverage)
([source](https://redirect.github.com/vitest-dev/vitest/tree/HEAD/packages/coverage-v8))
| devDependencies | pin | [`^4.1.2` →
`4.1.5`](https://renovatebot.com/diffs/npm/@vitest%2fcoverage-v8/4.1.5/4.1.5)
|
| [@vitest/coverage-v8](https://vitest.dev/guide/coverage)
([source](https://redirect.github.com/vitest-dev/vitest/tree/HEAD/packages/coverage-v8))
| devDependencies | pin | [`~3.2.4` →
`3.2.4`](https://renovatebot.com/diffs/npm/@vitest%2fcoverage-v8/3.2.4/3.2.4)
|
| [@vitest/coverage-v8](https://vitest.dev/guide/coverage)
([source](https://redirect.github.com/vitest-dev/vitest/tree/HEAD/packages/coverage-v8))
| devDependencies | pin | [`^1.6.1` →
`1.6.1`](https://renovatebot.com/diffs/npm/@vitest%2fcoverage-v8/1.6.1/1.6.1)
|
| [busboy](https://redirect.github.com/mscdex/busboy) | devDependencies
| pin | [`^1.6.0` →
`1.6.0`](https://renovatebot.com/diffs/npm/busboy/1.6.0/1.6.0) |
| [color](https://redirect.github.com/Qix-/color) | dependencies | pin |
[`^5.0.3` →
`5.0.3`](https://renovatebot.com/diffs/npm/color/5.0.3/5.0.3) |
| [glob](https://redirect.github.com/isaacs/node-glob) | devDependencies
| pin | [`^13.0.6` →
`13.0.6`](https://renovatebot.com/diffs/npm/glob/13.0.6/13.0.6) |
| [glob](https://redirect.github.com/isaacs/node-glob) | devDependencies
| pin | [`^10.5.0` →
`10.5.0`](https://renovatebot.com/diffs/npm/glob/10.5.0/10.5.0) |
| [node](https://redirect.github.com/nodejs/node) | final | pinDigest |
→ `8b1d14e` |
| [node](https://redirect.github.com/nodejs/node) | final | pinDigest |
→ `752ea8a` |
| [on-headers](https://redirect.github.com/jshttp/on-headers) |
dependencies | pin | [`^1.1.0` →
`1.1.0`](https://renovatebot.com/diffs/npm/on-headers/1.1.0/1.1.0) |
| redis | service | pinDigest |  → `352c1fd` |
| [sinon](https://sinonjs.org/)
([source](https://redirect.github.com/sinonjs/sinon)) | devDependencies
| pin | [`^21.1.1` →
`21.1.1`](https://renovatebot.com/diffs/npm/sinon/21.1.1/21.1.1) |
| [tailwindcss](https://tailwindcss.com)
([source](https://redirect.github.com/tailwindlabs/tailwindcss/tree/HEAD/packages/tailwindcss))
| devDependencies | pin | [`^4.2.2` →
`4.2.2`](https://renovatebot.com/diffs/npm/tailwindcss/4.2.2/4.2.2) |
| [tsc-alias](https://redirect.github.com/justkey007/tsc-alias.git)
([source](https://redirect.github.com/justkey007/tsc-alias)) |
devDependencies | pin | [`^1.8.17` →
`1.8.17`](https://renovatebot.com/diffs/npm/tsc-alias/1.8.17/1.8.17) |

⚠️ Renovate's pin functionality [does not
currently](https://redirect.github.com/renovatebot/renovate/issues/40288)
wire in the release age for a package, so the Minimum Release Age checks
can apply. You will need to manually validate the Minimum Release Age
for these package(s).

Add the preset `:preserveSemverRanges` to your config if you don't want
to pin your dependencies.

---

### Configuration

📅 **Schedule**: (in timezone Etc/UTC)

- Branch creation
  - Only on Sunday and Saturday (`* * * * 0,6`)
  - Between 12:00 AM and 12:59 PM, only on Monday (`* 0-12 * * 1`)
- Between 09:00 PM and 11:59 PM, Monday through Friday (`* 21-23 * *
1-5`)
- Between 12:00 AM and 04:59 AM, Tuesday through Saturday (`* 0-4 * *
2-6`)
- Automerge
  - Only on Sunday and Saturday (`* * * * 0,6`)
  - Between 12:00 AM and 12:59 PM, only on Monday (`* 0-12 * * 1`)
- Between 10:00 PM and 11:59 PM, Monday through Friday (`* 22-23 * *
1-5`)
- Between 12:00 AM and 04:59 AM, Tuesday through Saturday (`* 0-4 * *
2-6`)

🚦 **Automerge**: Disabled by config. Please merge this manually once you
are satisfied.

♻ **Rebasing**: Whenever PR is behind base branch, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about these
updates again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/TryGhost/Ghost).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4xMTAuMiIsInVwZGF0ZWRJblZlciI6IjQzLjE1OS4yIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Steve Larson <9larsons@gmail.com>
no ref

`apps/shade/test/.eslintrc.cjs` extended `plugin:ghost/test`
(non-TS-aware) while the test files are `.tsx`, and
`apps/shade/.eslintrc.cjs` was missing `root: true`. ESLint's computed
ruleset for shade test files therefore only had the `ghost` plugin
loaded — no `react`, `react-hooks`, or `@typescript-eslint` rules ran on
them. Confirmed with `eslint --print-config`.

The misconfig hid **74 pre-existing rule violations**. This PR is pure
lint hygiene — no production code or dependency changes.
ref
https://linear.app/ghost/issue/DES-1382/tags-list-page-header-migration

- Restructures `PageHeader` (`apps/shade`) into a single purpose
component
- Updates `List Page` component to incorporate PageHeader, ViewBar,
FilterBar with a sticky header
- Shade-only change — no consumer apps touched, the components changed
in this PR are not consumed in the Admin yet
@pull pull Bot locked and limited conversation to collaborators May 13, 2026
@pull pull Bot added the ⤵️ pull label May 13, 2026
@pull pull Bot merged commit 4303412 into code:main May 13, 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.

7 participants