TryGhost
OSS
Ghost
Sign in / Sign up
Open main menu
Ghost
GitHub
Overview
Runs
Analytics
Loading workspace stats
Loading workspace insights...
Statistics interval
7 days
30 days
Latest CI Pipeline Executions
Status
Fix filter
Filter
Fuzzy
Filter range
Sort by
Sort by
Start time
Sort ascending
Sort descending
Succeeded
29175
36012ea9 Pinned second describe to Ember + made logoutConfirmModal visibility-consistent Two remaining CodeRabbit findings from the initial review: - `disable-commenting.test.ts:175` second describe (`Ghost Admin - Disable Commenting Cache Invalidation`) wasn't pinned to Ember. This file lives under `members-legacy/` and reaches the member detail via `MemberDetailsPage`, so a future flag default flip would silently start routing it to the React screen. Added `test.use({labs: {memberDetailsReact: false}})` to match the first describe. - `member-details-page.ts:158-159` `logoutConfirmModal` was the only dual-tree `.or()` union without `.filter({visible: true})`. Added it for consistency with `disableCommentingModal`, `memberActionsButton`, `confirmDeleteButton` and `cancelDeleteButton`. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
by Peter Zimon
P
Succeeded
29175
199539cb Fixed member detail review findings from CodeRabbit Six actionable findings from CodeRabbit on PR #29175: 1. **isEnd hardcoded to MEMBER_ACTIVITY_LIMIT** (`admin-x-framework/src/api/members.ts`). Callers can pass a custom `limit` (the sidebar preview passes `'5'`), so a full page of results with a non-default limit was incorrectly reporting `isEnd=true`. Now reads the actual limit echoed back in the pagination meta and falls back to the default when absent. 2. **Attribution URL rendered as href without validation** (`member-detail-sidebar.tsx`). Attribution URLs come from external signup traffic (Referer, UTM) so a `javascript:` scheme could turn the sidebar's Page link into click-to-execute in the admin. Lifted `isSafeHref` out of the activity feed into a shared `is-safe-href.ts` and applied it here; unsafe URLs fall back to plain text. 3. **"received automated email: null" leak** (`member-event.ts:210/278`). `trimString(auto.subject)` returns `null` for a missing/empty subject and the template literal interpolated the string "null" into the feed. Guard the interpolation and render subject-less copy instead. Regression test in `member-event.test.ts` covers both missing and blank-whitespace subjects. 4. **UTC date formatting parity** (`member-subscriptions-section.tsx:43`). Sidebar uses `moment.utc`, subscriptions section used local time — so the same date rendered differently across the screen for admins outside UTC. Aligned to `moment.utc`. 5. **Parity test suite name convention** (`member-detail-parity.test.ts`). Renamed the loop-generated describes to `Ghost Admin - Member Detail Parity (Ember/React)` so they match the repo-wide `Ghost Admin - Feature` pattern from `e2e/CLAUDE.md`. 6. **Engagement testid + drop XPath parent-axis locator** (parity test). Added `data-testid='member-detail-engagement'` to the Ember `gh-member-details.hbs` engagement wrapper (React already had the same testid), then replaced `getByRole('heading').locator('..')` with `getByTestId('member-detail-engagement')` for a semantic, non-XPath scope. Skipped a 7th finding about `inert` typing in `member-labels-field.tsx` — CI already builds cleanly, and CodeRabbit's own web query notes React 18 supports `inert` as an ambient attribute via the JSX spread pattern used here. Verified: 179/179 unit tests green (one new regression test for #3). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
by Peter Zimon
P
Succeeded
29175
0bd98a97 Merge remote-tracking branch 'origin/main' into zimo/member-details-react-labs-flag
by Peter Zimon
P
Succeeded
29175
ee2bb99c Aligned the Activity empty-state padding with the Subscriptions card The Activity empty state used `CardContent py-4` directly around the `EmptyIndicator`, while the Subscriptions empty state wraps its indicator in an inner `py-4` div under a `CardContent pt-6` — different vertical rhythm meant the two cards didn't line up on the New member screen where they render one below the other. Mirrored the Subscriptions structure exactly (`pt-6` + inner `py-4` wrapper) so both empty cards share the same top gap between the section heading and the icon, and the same bottom gap under the copy. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
by Peter Zimon
P
Succeeded
29175
ee2bb99c Aligned the Activity empty-state padding with the Subscriptions card The Activity empty state used `CardContent py-4` directly around the `EmptyIndicator`, while the Subscriptions empty state wraps its indicator in an inner `py-4` div under a `CardContent pt-6` — different vertical rhythm meant the two cards didn't line up on the New member screen where they render one below the other. Mirrored the Subscriptions structure exactly (`pt-6` + inner `py-4` wrapper) so both empty cards share the same top gap between the section heading and the icon, and the same bottom gap under the copy. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
by Peter Zimon
P
Failed
29175
d6d9d4bf Fixed create-mode dirty check + pre-resolve save race exposed by review Adversarial review of the New member parity slices caught two defects. **1. Seeded newsletter defaults falsely marked the form as unsaved.** `hasUnsavedChanges` computed `serverSlice = getMemberEditableSlice({})` on every render in create mode — always the empty slice. After the seeding effect populated `draft.newsletters` with defaults, `dequal` disagreed and `useConfirmUnload` armed the browser prompt + `useBlocker` armed the Discard-changes AlertDialog on an untouched form. `lastServerSliceRef` is already updated in lockstep by both the create-init and seeding effects, so route the create-mode baseline through it. Edit mode still reads from the live query so background refetches surface external changes. **2. Fast Save before newsletters resolved sent `newsletters: []`.** Server default (`member-repository.js:460-464`) only kicks in when `newsletters` is ABSENT from the payload — an explicit `[]` records no
by Peter Zimon
P
Failed
29175
d6d9d4bf Fixed create-mode dirty check + pre-resolve save race exposed by review Adversarial review of the New member parity slices caught two defects. **1. Seeded newsletter defaults falsely marked the form as unsaved.** `hasUnsavedChanges` computed `serverSlice = getMemberEditableSlice({})` on every render in create mode — always the empty slice. After the seeding effect populated `draft.newsletters` with defaults, `dequal` disagreed and `useConfirmUnload` armed the browser prompt + `useBlocker` armed the Discard-changes AlertDialog on an untouched form. `lastServerSliceRef` is already updated in lockstep by both the create-init and seeding effects, so route the create-mode baseline through it. Edit mode still reads from the live query so background refetches surface external changes. **2. Fast Save before newsletters resolved sent `newsletters: []`.** Server default (`member-repository.js:460-464`) only kicks in when `newsletters` is ABSENT from the payload — an explicit `[]` records no
by Peter Zimon
P
Failed
29175
d6d9d4bf Fixed create-mode dirty check + pre-resolve save race exposed by review Adversarial review of the New member parity slices caught two defects. **1. Seeded newsletter defaults falsely marked the form as unsaved.** `hasUnsavedChanges` computed `serverSlice = getMemberEditableSlice({})` on every render in create mode — always the empty slice. After the seeding effect populated `draft.newsletters` with defaults, `dequal` disagreed and `useConfirmUnload` armed the browser prompt + `useBlocker` armed the Discard-changes AlertDialog on an untouched form. `lastServerSliceRef` is already updated in lockstep by both the create-init and seeding effects, so route the create-mode baseline through it. Edit mode still reads from the live query so background refetches surface external changes. **2. Fast Save before newsletters resolved sent `newsletters: []`.** Server default (`member-repository.js:460-464`) only kicks in when `newsletters` is ABSENT from the payload — an explicit `[]` records no
by Peter Zimon
P
Failed
29175
bee83e0e Refined the React member detail layout and typography - unified section headings (Signup info, Engagement, Newsletters, Subscriptions, Activity) to sentence case with a consistent `text-base` weight; sidebar headings gain a bottom hairline separator and extra top spacing so each block reads as a distinct section - widened the sidebar (`lg:w-80`) and added `lg:gap-12` between sidebar and main column so the right column doesn't feel cramped once labels/notes stretch - brought signup-info values (date, source, page) up to `text-foreground` so the label/value contrast matches Ember - swapped the header cogwheel for a horizontal ellipsis (`LucideIcon.Ellipsis`) and pinned it to `size-(--control-height)` (32px) so it lines up with the Save button — Shade's `icon` variant hardcodes 36px, which is a separate cleanup - flipped the default Save-button variant from `outline` to `default` so the resting/disabled state matches the primary button style; the unsaved-changes and error paths still override to default/destructive Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
by Peter Zimon
P
Succeeded
29175
bee83e0e Refined the React member detail layout and typography - unified section headings (Signup info, Engagement, Newsletters, Subscriptions, Activity) to sentence case with a consistent `text-base` weight; sidebar headings gain a bottom hairline separator and extra top spacing so each block reads as a distinct section - widened the sidebar (`lg:w-80`) and added `lg:gap-12` between sidebar and main column so the right column doesn't feel cramped once labels/notes stretch - brought signup-info values (date, source, page) up to `text-foreground` so the label/value contrast matches Ember - swapped the header cogwheel for a horizontal ellipsis (`LucideIcon.Ellipsis`) and pinned it to `size-(--control-height)` (32px) so it lines up with the Save button — Shade's `icon` variant hardcodes 36px, which is a separate cleanup - flipped the default Save-button variant from `outline` to `default` so the resting/disabled state matches the primary button style; the unsaved-changes and error paths still override to default/destructive Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
by Peter Zimon
P
Succeeded
29175
bee83e0e Refined the React member detail layout and typography - unified section headings (Signup info, Engagement, Newsletters, Subscriptions, Activity) to sentence case with a consistent `text-base` weight; sidebar headings gain a bottom hairline separator and extra top spacing so each block reads as a distinct section - widened the sidebar (`lg:w-80`) and added `lg:gap-12` between sidebar and main column so the right column doesn't feel cramped once labels/notes stretch - brought signup-info values (date, source, page) up to `text-foreground` so the label/value contrast matches Ember - swapped the header cogwheel for a horizontal ellipsis (`LucideIcon.Ellipsis`) and pinned it to `size-(--control-height)` (32px) so it lines up with the Save button — Shade's `icon` variant hardcodes 36px, which is a separate cleanup - flipped the default Save-button variant from `outline` to `default` so the resting/disabled state matches the primary button style; the unsaved-changes and error paths still override to default/destructive Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
by Peter Zimon
P
Succeeded
29175
bee83e0e Refined the React member detail layout and typography - unified section headings (Signup info, Engagement, Newsletters, Subscriptions, Activity) to sentence case with a consistent `text-base` weight; sidebar headings gain a bottom hairline separator and extra top spacing so each block reads as a distinct section - widened the sidebar (`lg:w-80`) and added `lg:gap-12` between sidebar and main column so the right column doesn't feel cramped once labels/notes stretch - brought signup-info values (date, source, page) up to `text-foreground` so the label/value contrast matches Ember - swapped the header cogwheel for a horizontal ellipsis (`LucideIcon.Ellipsis`) and pinned it to `size-(--control-height)` (32px) so it lines up with the Save button — Shade's `icon` variant hardcodes 36px, which is a separate cleanup - flipped the default Save-button variant from `outline` to `default` so the resting/disabled state matches the primary button style; the unsaved-changes and error paths still override to default/destructive Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
by Peter Zimon
P
Succeeded
29175
4f8760d2 Added the engagement stats section to the React member detail sidebar Ported the "Engagement" section from Ember's gh-member-details.hbs: Emails received / Emails opened / Average open rate. Follows the same visibility gate Ember uses — hidden when the site has no signup access or the default email-recipients setting is disabled — and mirrors Ember's empty-state copy and open-rate placeholder verbatim. Adversarial review flagged three real parity concerns; each is now fixed and covered by a parity test that runs the same assertion against both implementations: - engagementEnabled now waits for the settings query to resolve before turning on, so an invite-only or newsletter-disabled site can't briefly flash the section during a cold page load (Ember's synchronous settings service doesn't flash) - getFirstName mirrors Ember's first-name helper exactly: single- space split with no trim, so a leading-whitespace name produces the same first-token output on both sides - the empty-state check keeps its `?? 0` fallback because Ember Data serializes emailCount / emailOpenedCount with defaultValue: 0 in ghost/admin/app/models/member.js; a new regression test injects a payload with the email fields omitted and asserts BOTH React and Ember show the same empty-state copy
by Peter Zimon
P
Succeeded
29175
df80ac3b Refined the React member detail form layout - swapped the top-row two-column grid for a stacked left column (name, email, labels) and a full-height note field on the right so the note area gets the space it needs - built the note field on Shade's InputGroup so the textarea shares the same inputSurface chrome as the other inputs, sits borderless inside the wrapper, and lets an InputGroupAddon act as a proper footer for the character counter - the counter footer stays out of the textarea's scroll region, so the macOS floating scrollbar doesn't get hidden behind it and scrolled text can't overlap the counter anymore - the used-count digit walks a green → yellow → red gradient as the note fills up, giving a heads-up before the limit instead of Ember's binary green/red flip
by Peter Zimon
P
Succeeded
29175
b0c5c33a Hardened the labs-flag retrofit against adversarial-review findings - runtime gate no longer defaults to EmberFallback while the config query is still loading; renders null for one paint instead so a cold hit of /members/:id with the flag on doesn't briefly un-hide #ember-app and kick off an aborted Ember transition - new parity test asserts the sole-tree invariant explicitly (when the flag is on Ember must not render its member subtree; when off the React root must not mount) so a regression on either side trips a red test instead of silently narrowing the honesty guarantee to whichever tree is still visible - new parity test covers /members/new so the create-mode contract is validated under both implementations, not just Ember via the restored legacy specs - name-render parity assertion now anchors on the title elements (data-test-screen-title in Ember, data-testid="member-detail-title" in React) instead of a loose text match that would tolerate the name only appearing in an aria-hidden slot - sign-out confirm click is scoped to the logout-member modal via the two implementations' modal testids so a future sidebar regression can't let the click bleed into the account-owner "Sign out" button - shared page-object modal locators now filter to visible consistently — the disable-commenting modal picked up the same guardrail as the other dual-tree locators
by Peter Zimon
P
Succeeded
29175
b0c5c33a Hardened the labs-flag retrofit against adversarial-review findings - runtime gate no longer defaults to EmberFallback while the config query is still loading; renders null for one paint instead so a cold hit of /members/:id with the flag on doesn't briefly un-hide #ember-app and kick off an aborted Ember transition - new parity test asserts the sole-tree invariant explicitly (when the flag is on Ember must not render its member subtree; when off the React root must not mount) so a regression on either side trips a red test instead of silently narrowing the honesty guarantee to whichever tree is still visible - new parity test covers /members/new so the create-mode contract is validated under both implementations, not just Ember via the restored legacy specs - name-render parity assertion now anchors on the title elements (data-test-screen-title in Ember, data-testid="member-detail-title" in React) instead of a loose text match that would tolerate the name only appearing in an aria-hidden slot - sign-out confirm click is scoped to the logout-member modal via the two implementations' modal testids so a future sidebar regression can't let the click bleed into the account-owner "Sign out" button - shared page-object modal locators now filter to visible consistently — the disable-commenting modal picked up the same guardrail as the other dual-tree locators
by Peter Zimon
P
Previous page
Previous
Next
Next page