Skip to content

[pull] main from TryGhost:main#1117

Merged
pull[bot] merged 9 commits intocode:mainfrom
TryGhost:main
May 5, 2026
Merged

[pull] main from TryGhost:main#1117
pull[bot] merged 9 commits intocode:mainfrom
TryGhost:main

Conversation

@pull
Copy link
Copy Markdown

@pull pull Bot commented May 5, 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 : )

kevinansfield and others added 9 commits May 5, 2026 10:51
no issue

## Summary
- Removed the Linear issue triage Agentic Workflow source markdown
- Removed the generated GitHub Actions lock workflow for the Linear triage agent

## Why
The Linear triage workflow has been failing, and this ends the initial
automation experiment instead of continuing scheduled runs.
Closes
https://linear.app/ghost/issue/DES-1166/support-escalation-re-email-needs-verification

Previously we would imply that newsletter sending was still possible
when access was set to Nobody. Access = Nobody disables newsletter
sending. The messaging has been updated to reflect that. The disabled
state has also been improved.

| Before | After |
|--------|--------|
| <img width="714" height="353" alt="Screenshot 2026-05-01 at 12 53 36"
src="https://github.com/user-attachments/assets/8e5cb169-53d1-4f29-a9b2-76573678273a"
/> | <img width="707" height="375" alt="Screenshot 2026-05-01 at 12 53
09"
src="https://github.com/user-attachments/assets/77bd1013-fe0b-44f2-8baa-92da92c94a07"
/> |
…7668)

ref https://linear.app/ghost/issue/BER-3600/design-iteration

Refreshed every screen in the gift subscription flow — selection,
confirmation, redemption, and the magic link state when redeeming — with
a single shared 50/50 split layout: action on the left, gift card
preview on the right.

### What's new

- **Unified gift card preview** across all four screens — same site icon
+ duration + tier, brand-tinted gradient, cross ribbons, wrapped-bow
SVG, subtle cursor-follow tilt.
- **Selection screen**: title + duration toggle + tier radio list + pill
CTA on the left; benefits below the card on the right with smooth height
animation when switching tiers.
- **Confirmation screen**: shareable link pill with copy button on the
left; reused gift card on the right with collapsible "Gift details"
toggle.
- **Redemption screen**: title "A gift, just for you" + name/email form
+ redeem button; same toggle reveals tier benefits with card tilt that
makes them look like they're sliding out from behind the card.
- **Magic link in gift mode**: when reached via gift redemption, swaps
the centered modal for the same 50/50 layout so the gift card stays put
across the form → "Now check your email!" transition.
ref https://linear.app/ghost/issue/BER-3565
ref https://linear.app/ghost/issue/BER-3542
ref #27615
ref https://ghost.slack.com/archives/CJBJ3MZFD/p1777972226248739

## What changed

The migration
`2026-04-29-12-13-44-add-batch-id-to-members-status-events.js` now
passes `{algorithm: 'auto'}` to `createAddColumnMigration`, so the
emitted DDL is `ALTER TABLE members_status_events ADD COLUMN
batch_id...` omits the default `ALGORITHM=COPY`.

## Why

`createAddColumnMigration` delegates to `commands.addColumn`, which on
MySQL appends `ALGORITHM=COPY` by default unless `{algorithm: 'auto'}`
is explicitly passed. `ALGORITHM=COPY` rebuilds the entire table: it
creates a new copy with the new column, copies every row across, then
swaps it in. The cost is O(rows), and on `members_status_events` — which
gets one row per member status change — this scales with the size of the
member base.

We hit this on a staging site with ~6M `members_status_events` rows on
boot:

```
[MIGRATE] Migration error:  alter table `members_status_events` add `batch_id` varchar(24) null, algorithm=copy - Connection lost: The server closed the connection.
```

`ALGORITHM=AUTO` lets MySQL pick the best strategy. For a nullable
trailing-column add on MySQL 8.0+ it picks `INSTANT`, which is a
metadata-only change and effectively constant-time regardless of table
size.

This isn't a new conclusion — [#25018 (`Added utm_ columns to events
tables`)](2ea046c)
introduced the `algorithm` option for exactly this reason after
observing 60–250s migrations on a 2M-row `members_created_events` table
vs. 2–3s when MySQL was allowed to choose. All the column adds in that
PR (and the canonical `add-utm-fields.js` migration) use `{algorithm:
'auto'}`.

## Why we're modifying an existing migration

We don't normally do this —
`.agents/skills/create-database-migration/rules.md` states migrations
are immutable once on `main`. We're making an exception here because:

1. **A new migration cannot fix the original.** The bad `ALTER TABLE`
runs first when production upgrades through `6.36/`. Adding a fix in a
later folder doesn't change that.
2. **The schema is unchanged.** Only the MySQL execution hint differs.
`schema.js` is untouched, so the integrity hash isn't bumped, and the
end-state column definition is identical.
3. **The migration helper is idempotent.** `createColumnMigration`
checks `hasColumn` and skips if the column already exists — so sites
that already ran the original version on `6.36.0` simply no-op when
redeployed against this commit. They don't see the migration twice and
there's no schema drift between sites that ran the old version and sites
that will run the new one.

In short: existing sites that already migrated are unaffected; sites
that haven't yet (including production) get the fast path.
## Summary

This PR consolidates core components in Shade. It restructures three
existing components into three primitives that later on will compose
into Shade components to eliminate duplication. **No existing apps
(Stats, Posts etc.) change in this PR**, so risk is contained to the new
files.

- **`TrendBadge`** — value + direction (up / down / same) with optional
tooltip. Replaces the duplicated value+arrow markup currently inlined in
`card.tsx` (`KpiCardHeaderValue`) and `tabs.tsx` (`KpiTabValue`).
- **`MetricValue`** — generic label + large value + "after" slot, sized
`md` / `lg`. The two KPI wrappers above will compose this around
`TrendBadge` in Phase 3.
- **`surfaceField(mode)`** — shared border / bg / radius / focus-ring /
invalid style recipe which is currently copy-pasted across `input.tsx`,
`textarea.tsx` and `input-group.tsx`. Exposed as a recipe helper rather
than a wrapper component since every consumer already has its own root
element. `'self'` mode applies directly to a focusable element;
`'within'` mode applies to a wrapper that contains a focusable child.

ref https://linear.app/tryghost/issue/DES-1343

## Why now

Shade already has solid tokens and primitives. The next weak spot is the
component layer itself. `apps/shade/src/components/ui/` has duplicated
visual recipes and KPI markup repeated across files. This milestone
tightens that. Phase 1 ships only additive primitives so each subsequent
phase can refactor one consumer at a time behind a green smoke test.
## Summary

This PR is part of consolidating core components in Shade. **Stacked on
#27565.** It finishes what the first PR set up: the new primitives
(`TrendBadge`, `MetricValue`, `inputSurface`) are in place; this one
cleans up where things live.

- KPI wrappers moved out of `ui/card.tsx` and `ui/tabs.tsx` into
`features/kpi/`. New `KpiTabs` / `KpiTabsList` / `KpiTabsContent`
wrappers added so future code can stop writing `<Tabs variant='kpis'>`
directly.
- `gh-chart.tsx` (Ghost-flavoured analytics chart) moved out of `ui/`
into `features/charts/`.
- `filters.tsx` (full filter-builder feature) moved out of `ui/` into
`features/filters/`.
- `Foundations` pinned at the top of the Storybook sidebar order. Grep
guardrail run: `ui/` is clean of product-domain words in production
code.

ref https://linear.app/tryghost/issue/DES-1343

## Why now

This is the consumer-affecting half of Shade core component work: they
**don't** affect any consumer files. All consumers import via the
`@tryghost/shade/components` and `@tryghost/shade/patterns` entrypoints,
and those were updated to point at the new internal paths.

The `kpis` cva variant remains in `ui/tabs.tsx` as an internal
implementation detail used only by `features/kpi/kpi-tabs.tsx`. Removing
it cleanly would have required baking a long override class string into
the wrapper and fighting tailwind-merge to neutralize segmented defaults
— not worth the regression risk.

## Trade-offs

- `ui/multi-select-combobox.tsx` imports types from `features/filters/`.
That's a layering inversion (`ui/` depending on `features/`) —
pre-existing, out of scope to fix here.
- TypeScript path aliases like `@/components/...` survive into emitted
`.d.ts` files and break consumers (their tsconfig doesn't share shade's
path mapping). All `features/*` files in this PR use **relative
imports** for cross-shade-file type references for this reason.
…tion (#27669)

## Summary

Rewrites the Shade Storybook documentation pages so they read like prose
written by a human, not bullet points generated by a machine. Adds a
proper **Terms** section to the Architecture page that defines the words
we throw around (tokens, primitives, components, recipes, layouts,
features, app).

ref https://linear.app/tryghost/issue/DES-1343

## What changed

Six docs rewritten:

- **introduction.mdx** — Welcome page. What Shade is, who it's for,
getting set up.
- **architecture.mdx** — How the layered system works. **New Terms
section** defining each layer with examples. Includes the
file-system-vs-entrypoint mismatch (`features/` on disk vs
`@tryghost/shade/patterns`) that's been a quiet source of confusion.
- **tokens.mdx** — What tokens are, how they're defined, how to consume
them.
- **primitives-guide.mdx** — Why primitives exist, when to reach for
each, prop reference, migration examples.
- **component-contracts.mdx** — What you can rely on from a Shade
component (rules and guarantees).
- **contributing.mdx** — Practical guide for adding to or changing
Shade.

One doc left alone:

- **migration-root-imports.mdx** — Already a tight reference table;
that's the right format for it.
no ref
- `pnpm audit` reported 15 axios advisories — 5 high, 9 moderate, 1 low
— all in versions `<1.15.2`. They're the prototype-pollution / SSRF /
header-injection cluster published in the past two weeks.
- Two resolved versions of axios in the tree: `1.13.6` (via `nx`, root
devDep) and `1.15.0` (via `@slack/webhook`, ghost/core). Both are
vulnerable. The override `axios@<1.15.2: ^1.15.2` pins both up to a
patched version within the same major.
- Same-major bump; no caller API changes. Pure lockfile/override change
— no source files touched.
no ref

Three vite advisories landed against apps/admin's pinned `7.1.12` — two
high-severity (dev-server WebSocket file-read, `server.fs.deny`
query-string bypass) and one moderate (path traversal in optimized-deps
`.map` handling). All three are patched in `7.3.2`, which is a
same-major patch bump within v7.
@pull pull Bot locked and limited conversation to collaborators May 5, 2026
@pull pull Bot added the ⤵️ pull label May 5, 2026
@pull pull Bot merged commit 4703b16 into code:main May 5, 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.

6 participants