From 7ade69a2de43f68145fa48999e6677e29686fe5a Mon Sep 17 00:00:00 2001 From: Filipe Varela Date: Tue, 5 May 2026 11:23:13 -0400 Subject: [PATCH 1/5] Activity Log: revert free-tier search overlay to use inert MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Per #48527 (and the original PR #48418 review): the `aria-disabled` + `tabindex="-1"` + `pointer-events: none` triple was chosen to keep the `title` upgrade tooltip working in Firefox. We're accepting that tradeoff and going back to the cleaner `inert` attribute, which blocks pointer + keyboard interaction and hides descendants from the a11y tree in one shot. Firefox suppresses `title` tooltips inside an inert subtree, so the upgrade nudge won't appear there — accepted per the issue. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../free-tier-search-overlay-use-inert | 4 +++ .../src/js/components/ActivityLog/index.tsx | 32 +++++++------------ .../packages/activity-log/src/js/style.scss | 20 ++++-------- 3 files changed, 23 insertions(+), 33 deletions(-) create mode 100644 projects/packages/activity-log/changelog/free-tier-search-overlay-use-inert diff --git a/projects/packages/activity-log/changelog/free-tier-search-overlay-use-inert b/projects/packages/activity-log/changelog/free-tier-search-overlay-use-inert new file mode 100644 index 000000000000..ed33d8e84a71 --- /dev/null +++ b/projects/packages/activity-log/changelog/free-tier-search-overlay-use-inert @@ -0,0 +1,4 @@ +Significance: patch +Type: changed + +Activity Log: free-tier DataViews search overlay now uses the `inert` attribute instead of `aria-disabled` + `tabindex="-1"` + `pointer-events: none`. diff --git a/projects/packages/activity-log/src/js/components/ActivityLog/index.tsx b/projects/packages/activity-log/src/js/components/ActivityLog/index.tsx index 6ea493cbfce4..bf7f94a52efb 100644 --- a/projects/packages/activity-log/src/js/components/ActivityLog/index.tsx +++ b/projects/packages/activity-log/src/js/components/ActivityLog/index.tsx @@ -134,15 +134,15 @@ export default function ActivityLog() { // On free tier, neutralize DataViews' real search + filter cluster // (the `.dataviews__search` Stack rendered by `DataViews`'s default // UI). We let DataViews ship its own toolbar so the page tracks - // upstream changes for free, then attach: `aria-disabled` for - // assistive tech, a `title` attribute that surfaces the upgrade - // nudge as a native browser tooltip on hover, and `tabindex="-1"` - // on every focusable descendant so the cluster is unreachable via - // keyboard. Pointer-event blocking on the children is handled in - // CSS via the `[aria-disabled="true"]` rule. - // `MutationObserver` re-applies after DataViews remounts the - // toolbar / re-renders the input (e.g., on initial fetch resolution - // or layout switch) so React's render doesn't strip the attributes. + // upstream changes for free, then mark the cluster `inert` — which + // blocks pointer + keyboard interaction and removes descendants from + // the a11y tree in one shot — and add a `title` attribute that + // surfaces the upgrade nudge as a native browser tooltip on hover. + // Tradeoff: Firefox suppresses `title` tooltips inside an inert + // subtree, so the nudge doesn't appear there; accepted per #48527. + // `MutationObserver` re-applies after DataViews remounts the toolbar + // / re-renders the input (e.g., on initial fetch resolution or + // layout switch) so React's render doesn't strip the attributes. useEffect( () => { if ( hasActivityLogsAccess ) { return; @@ -157,20 +157,12 @@ export default function ActivityLog() { const apply = ( root: ParentNode ) => { const cluster = root.querySelector< HTMLElement >( '.dataviews__search' ); - if ( ! cluster ) { + if ( ! cluster || cluster.hasAttribute( 'inert' ) ) { return; } - if ( cluster.getAttribute( 'aria-disabled' ) !== 'true' ) { - cluster.setAttribute( 'aria-disabled', 'true' ); - cluster.setAttribute( 'title', tooltipText ); - } - - cluster.querySelectorAll< HTMLElement >( 'input, button, [tabindex]' ).forEach( el => { - if ( el.getAttribute( 'tabindex' ) !== '-1' ) { - el.setAttribute( 'tabindex', '-1' ); - } - } ); + cluster.setAttribute( 'inert', '' ); + cluster.setAttribute( 'title', tooltipText ); }; apply( wrapper ); diff --git a/projects/packages/activity-log/src/js/style.scss b/projects/packages/activity-log/src/js/style.scss index 0890c48b6fbf..153c486bfa7e 100644 --- a/projects/packages/activity-log/src/js/style.scss +++ b/projects/packages/activity-log/src/js/style.scss @@ -57,21 +57,15 @@ body.jetpack_page_jetpack-activity-log { } } - // Visual + click-blocking half of the free-tier "overlay" approach: - // the effect in ActivityLog/index.tsx toggles `aria-disabled` and - // `tabindex="-1"` on DataViews' real `.dataviews__search` cluster - // (search input + filter toggle), and adds a `title` for the - // upgrade tooltip. This rule dims the cluster, shows a not-allowed - // cursor, and shuts off pointer events on the controls inside so - // clicks fall through to the cluster (keeping it hoverable for the - // browser-native title tooltip). - .dataviews__search[aria-disabled="true"] { + // Visual half of the free-tier "overlay" approach: the effect in + // ActivityLog/index.tsx marks DataViews' real `.dataviews__search` + // cluster (search input + filter toggle) `inert` and adds a `title` + // for the upgrade tooltip. `inert` natively blocks pointer + + // keyboard interaction and hides descendants from a11y, so this + // rule only carries the visual disabled state. + .dataviews__search[inert] { opacity: 0.5; cursor: not-allowed; - - > * { - pointer-events: none; - } } // DataViews renders its own surface; don't double-pad it. From f656bedea8b58c590ac3f85dec57fa4a1c0f45e5 Mon Sep 17 00:00:00 2001 From: Filipe Varela Date: Tue, 5 May 2026 11:24:37 -0400 Subject: [PATCH 2/5] Activity Log: refresh free-tier upsell illustration + lowercase title MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Swap the upsell callout illustration for the Jetpack-branded version (per https://github.com/Automattic/jetpack/pull/48418#issuecomment-4357424303). Filename kept as `activity-logs-callout-illustration.svg` so the webpack import in `UpsellCallout.tsx` resolves untouched. - Lowercase "activity logs" in the upsell title — sentence case to match Jetpack copy conventions. - Drop the inert-revert changelog from the previous commit; that change has no user-visible behavior shift, so a single user-benefit entry covering the visible refresh suffices. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../free-tier-search-overlay-use-inert | 4 - .../upsell-illustration-and-title-casing | 4 + .../components/ActivityLog/UpsellCallout.tsx | 2 +- .../activity-logs-callout-illustration.svg | 79 +++++++++---------- 4 files changed, 41 insertions(+), 48 deletions(-) delete mode 100644 projects/packages/activity-log/changelog/free-tier-search-overlay-use-inert create mode 100644 projects/packages/activity-log/changelog/upsell-illustration-and-title-casing diff --git a/projects/packages/activity-log/changelog/free-tier-search-overlay-use-inert b/projects/packages/activity-log/changelog/free-tier-search-overlay-use-inert deleted file mode 100644 index ed33d8e84a71..000000000000 --- a/projects/packages/activity-log/changelog/free-tier-search-overlay-use-inert +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: changed - -Activity Log: free-tier DataViews search overlay now uses the `inert` attribute instead of `aria-disabled` + `tabindex="-1"` + `pointer-events: none`. diff --git a/projects/packages/activity-log/changelog/upsell-illustration-and-title-casing b/projects/packages/activity-log/changelog/upsell-illustration-and-title-casing new file mode 100644 index 000000000000..03de6c28efde --- /dev/null +++ b/projects/packages/activity-log/changelog/upsell-illustration-and-title-casing @@ -0,0 +1,4 @@ +Significance: patch +Type: changed + +Activity Log: refreshed the free-tier upsell illustration to match Jetpack's branding. diff --git a/projects/packages/activity-log/src/js/components/ActivityLog/UpsellCallout.tsx b/projects/packages/activity-log/src/js/components/ActivityLog/UpsellCallout.tsx index 0bf291fb9a29..08a7f98fddd3 100644 --- a/projects/packages/activity-log/src/js/components/ActivityLog/UpsellCallout.tsx +++ b/projects/packages/activity-log/src/js/components/ActivityLog/UpsellCallout.tsx @@ -78,7 +78,7 @@ export function UpsellCallout() {

- { __( 'Track every action with Activity logs', 'jetpack-activity-log' ) } + { __( 'Track every action with activity logs', 'jetpack-activity-log' ) }

{ __( diff --git a/projects/packages/activity-log/src/js/components/ActivityLog/activity-logs-callout-illustration.svg b/projects/packages/activity-log/src/js/components/ActivityLog/activity-logs-callout-illustration.svg index e6b0fb17ee8c..08990245ca79 100644 --- a/projects/packages/activity-log/src/js/components/ActivityLog/activity-logs-callout-illustration.svg +++ b/projects/packages/activity-log/src/js/components/ActivityLog/activity-logs-callout-illustration.svg @@ -1,52 +1,45 @@ - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + - - - - - - + + + + - - - + + + + - - - + + + + - - - + + + + - - - + + + + - - - + + + - - - - From a54032949d3bee374c76fbcd067462fd5f77dc1a Mon Sep 17 00:00:00 2001 From: Filipe Varela Date: Tue, 5 May 2026 14:12:43 -0400 Subject: [PATCH 3/5] Activity Log: deep-link Manage backup action to Calypso restore flow MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The DataViews "Manage backup" row action was a disabled placeholder waiting for a future Backup wp-admin page. Wire it up to open Calypso's Backup restore flow for the row's rewind point in a new tab — `https://wordpress.com/backup/{calypsoSlug}/restore/{rewindId}` — which gives users an actionable path today without waiting on the in-progress port. - `actions.tsx`: read `calypsoSlug` from Initial_State at module load, render the action only when the row is rewindable AND has a `rewindId` AND a slug is available, and on click `window.open` the Calypso URL with `noopener,noreferrer`. Also accept an optional `tracks` handle and emit a `jetpack_activity_log_manage_backup_click` event. - `index.tsx`: pass `tracks` through to `useActivityActions`, and drop the "Coming soon" tooltip MutationObserver effect — the action is no longer disabled so the textContent-matching DOM hack is obsolete. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../manage-backup-action-deep-link-calypso | 4 ++ .../src/js/components/ActivityLog/actions.tsx | 51 ++++++++++++++----- .../src/js/components/ActivityLog/index.tsx | 40 +-------------- 3 files changed, 44 insertions(+), 51 deletions(-) create mode 100644 projects/packages/activity-log/changelog/manage-backup-action-deep-link-calypso diff --git a/projects/packages/activity-log/changelog/manage-backup-action-deep-link-calypso b/projects/packages/activity-log/changelog/manage-backup-action-deep-link-calypso new file mode 100644 index 000000000000..37729b926e4a --- /dev/null +++ b/projects/packages/activity-log/changelog/manage-backup-action-deep-link-calypso @@ -0,0 +1,4 @@ +Significance: patch +Type: changed + +Activity Log: the "Manage backup" row action now opens the Calypso Backup restore flow for that point in time, instead of being a disabled placeholder. diff --git a/projects/packages/activity-log/src/js/components/ActivityLog/actions.tsx b/projects/packages/activity-log/src/js/components/ActivityLog/actions.tsx index 90b604bf6a32..c5f1a3902076 100644 --- a/projects/packages/activity-log/src/js/components/ActivityLog/actions.tsx +++ b/projects/packages/activity-log/src/js/components/ActivityLog/actions.tsx @@ -5,24 +5,42 @@ import { useMemo } from 'react'; import type { Activity } from './types'; import type { Action } from '@wordpress/dataviews'; +interface InitialStateWithCalypsoSlug { + jetpackStatus?: { calypsoSlug?: string }; +} + +declare const JPACTIVITYLOG_INITIAL_STATE: InitialStateWithCalypsoSlug | undefined; + +// Read once at module load; the value doesn't change within a session. +const calypsoSlug: string = + ( typeof JPACTIVITYLOG_INITIAL_STATE !== 'undefined' + ? JPACTIVITYLOG_INITIAL_STATE?.jetpackStatus?.calypsoSlug + : undefined ) ?? ''; + +type Tracks = { recordEvent: ( name: string, props?: Record< string, unknown > ) => void }; + type UseActivityActionsOptions = { isLoading: boolean; + tracks?: Tracks; }; /** - * Row actions for the DataViews table. Phase 5 wires the "Manage backup" - * action into the Backup package's admin page; for now the action is - * present but disabled so the column space is preserved and the planned - * feature is visible. + * Row actions for the DataViews table. The single primary action deep- + * links into the Calypso Backup restore flow for the row's rewind point + * (`https://wordpress.com/backup/{slug}/restore/{rewindId}`) and opens + * in a new tab. Eligibility requires `activityIsRewindable`, a + * `rewindId`, and a `calypsoSlug` from Initial_State; rows missing any + * of those don't render the action. * * @param options - Hook options. * @param options.isLoading - Whether the list is currently fetching. Kept - * in the API so Phase 5 doesn't need to refactor - * the call site. + * in the API for symmetry with the call site. + * @param options.tracks - Optional analytics handle for the click event. * @return The actions array for ``. */ export function useActivityActions( { isLoading, + tracks, }: UseActivityActionsOptions ): Action< Activity >[] { return useMemo( () => { const backupAction: Action< Activity > = { @@ -30,11 +48,20 @@ export function useActivityActions( { isPrimary: true, label: __( 'Manage backup', 'jetpack-activity-log' ), icon: , - // Phase 5: enable and deep-link into the Backup package's admin page. - disabled: true, - isEligible: item => item.activityIsRewindable, - callback: async () => { - /* no-op until Phase 5 */ + isEligible: item => Boolean( item.activityIsRewindable && item.rewindId && calypsoSlug ), + callback: async items => { + const item = items[ 0 ]; + if ( ! item?.rewindId || ! calypsoSlug ) { + return; + } + const url = `https://wordpress.com/backup/${ encodeURIComponent( + calypsoSlug + ) }/restore/${ encodeURIComponent( item.rewindId ) }`; + tracks?.recordEvent( 'jetpack_activity_log_manage_backup_click', { + rewind_id: item.rewindId, + activity_name: item.activityName, + } ); + window.open( url, '_blank', 'noopener,noreferrer' ); }, }; @@ -42,5 +69,5 @@ export function useActivityActions( { void isLoading; return [ backupAction ]; - }, [ isLoading ] ); + }, [ isLoading, tracks ] ); } diff --git a/projects/packages/activity-log/src/js/components/ActivityLog/index.tsx b/projects/packages/activity-log/src/js/components/ActivityLog/index.tsx index bf7f94a52efb..521992f52e98 100644 --- a/projects/packages/activity-log/src/js/components/ActivityLog/index.tsx +++ b/projects/packages/activity-log/src/js/components/ActivityLog/index.tsx @@ -93,44 +93,6 @@ export default function ActivityLog() { const { tracks } = useAnalytics(); const wrapperRef = useRef< HTMLDivElement >( null ); - // DataViews' `Action` API doesn't expose a tooltip prop, so the - // disabled "Manage backup" stub renders without any hint as to *why* - // it can't be clicked. Attach a `title` to those buttons after each - // render so users hovering get the "Coming soon" context — and - // screen readers pick it up too. The MutationObserver re-runs on - // pagination / filter changes, when DataViews swaps the row DOM. - // - // TODO(#48236): drop this whole effect. Once the Backup wp-admin - // page lands the action stops being a stub and the row will render - // as an enabled link, so this textContent-matching DOM hack — which - // is fragile across translations and re-fires on every DataViews - // mutation — won't be needed at all. - useEffect( () => { - const wrapper = wrapperRef.current; - if ( ! wrapper ) { - return; - } - const manageBackupLabel = __( 'Manage backup', 'jetpack-activity-log' ); - const tooltipText = __( 'Coming soon', 'jetpack-activity-log' ); - const apply = ( root: ParentNode ) => { - const buttons = root.querySelectorAll< HTMLButtonElement >( - '.dataviews-item-actions button[disabled]' - ); - buttons.forEach( btn => { - if ( - btn.textContent?.trim() === manageBackupLabel && - btn.getAttribute( 'title' ) !== tooltipText - ) { - btn.setAttribute( 'title', tooltipText ); - } - } ); - }; - apply( wrapper ); - const observer = new MutationObserver( () => apply( wrapper ) ); - observer.observe( wrapper, { subtree: true, childList: true } ); - return () => observer.disconnect(); - }, [] ); - // On free tier, neutralize DataViews' real search + filter cluster // (the `.dataviews__search` Stack rendered by `DataViews`'s default // UI). We let DataViews ship its own toolbar so the page tracks @@ -274,7 +236,7 @@ export default function ActivityLog() { activityLogTypes: groupCountsData?.groups, } ); - const actions = useActivityActions( { isLoading: isFetching } ); + const actions = useActivityActions( { isLoading: isFetching, tracks } ); const onChangeView = useCallback( ( next: View ) => { From 7474d8e32e1f6f56a24477f6dd02c8cacbd002fa Mon Sep 17 00:00:00 2001 From: Filipe Varela Date: Tue, 5 May 2026 14:16:20 -0400 Subject: [PATCH 4/5] Activity Log: point Manage backup at cloud.jetpack.com instead of WPCOM Jetpack Cloud (cloud.jetpack.com) is the Jetpack-branded analog of the Calypso Backup flow and is what we want users to land on from a Jetpack admin surface. Swap the deep-link host accordingly: `https://cloud.jetpack.com/backup/{calypsoSlug}/restore/{rewindId}`. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../changelog/manage-backup-action-deep-link-calypso | 2 +- .../activity-log/src/js/components/ActivityLog/actions.tsx | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/projects/packages/activity-log/changelog/manage-backup-action-deep-link-calypso b/projects/packages/activity-log/changelog/manage-backup-action-deep-link-calypso index 37729b926e4a..0de46b467fd0 100644 --- a/projects/packages/activity-log/changelog/manage-backup-action-deep-link-calypso +++ b/projects/packages/activity-log/changelog/manage-backup-action-deep-link-calypso @@ -1,4 +1,4 @@ Significance: patch Type: changed -Activity Log: the "Manage backup" row action now opens the Calypso Backup restore flow for that point in time, instead of being a disabled placeholder. +Activity Log: the "Manage backup" row action now opens the Jetpack Cloud Backup restore flow for that point in time, instead of being a disabled placeholder. diff --git a/projects/packages/activity-log/src/js/components/ActivityLog/actions.tsx b/projects/packages/activity-log/src/js/components/ActivityLog/actions.tsx index c5f1a3902076..f2c3f4c56844 100644 --- a/projects/packages/activity-log/src/js/components/ActivityLog/actions.tsx +++ b/projects/packages/activity-log/src/js/components/ActivityLog/actions.tsx @@ -27,7 +27,7 @@ type UseActivityActionsOptions = { /** * Row actions for the DataViews table. The single primary action deep- * links into the Calypso Backup restore flow for the row's rewind point - * (`https://wordpress.com/backup/{slug}/restore/{rewindId}`) and opens + * (`https://cloud.jetpack.com/backup/{slug}/restore/{rewindId}`) and opens * in a new tab. Eligibility requires `activityIsRewindable`, a * `rewindId`, and a `calypsoSlug` from Initial_State; rows missing any * of those don't render the action. @@ -54,7 +54,7 @@ export function useActivityActions( { if ( ! item?.rewindId || ! calypsoSlug ) { return; } - const url = `https://wordpress.com/backup/${ encodeURIComponent( + const url = `https://cloud.jetpack.com/backup/${ encodeURIComponent( calypsoSlug ) }/restore/${ encodeURIComponent( item.rewindId ) }`; tracks?.recordEvent( 'jetpack_activity_log_manage_backup_click', { From 4308193743722c1c59a685412adad4c25c09dbac Mon Sep 17 00:00:00 2001 From: Filipe Varela Date: Tue, 5 May 2026 14:41:47 -0400 Subject: [PATCH 5/5] Activity Log: rename action to "Restore backup", flag link as temporary MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Label "Manage backup" → "Restore backup" — the button opens the Backup restore flow, so the label should describe that. - Tracks event renamed in lockstep: jetpack_activity_log_manage_backup_click → jetpack_activity_log_restore_backup_click. - Document in the hook's docblock that the cloud.jetpack.com deep-link is a stop-gap until the Backup wp-admin port (#48236) lands. After that ships, every row action here should point at the in-admin Backup page instead of cloud.jetpack.com so users stay inside their own wp-admin. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../restore-backup-action-label-and-temp-doc | 4 ++++ .../src/js/components/ActivityLog/actions.tsx | 20 ++++++++++++------- 2 files changed, 17 insertions(+), 7 deletions(-) create mode 100644 projects/packages/activity-log/changelog/restore-backup-action-label-and-temp-doc diff --git a/projects/packages/activity-log/changelog/restore-backup-action-label-and-temp-doc b/projects/packages/activity-log/changelog/restore-backup-action-label-and-temp-doc new file mode 100644 index 000000000000..74cdc176759e --- /dev/null +++ b/projects/packages/activity-log/changelog/restore-backup-action-label-and-temp-doc @@ -0,0 +1,4 @@ +Significance: patch +Type: changed + +Activity Log: rename the row action to "Restore backup" so the label matches what clicking it actually does — open the restore flow. diff --git a/projects/packages/activity-log/src/js/components/ActivityLog/actions.tsx b/projects/packages/activity-log/src/js/components/ActivityLog/actions.tsx index f2c3f4c56844..a0ce3ce41bd0 100644 --- a/projects/packages/activity-log/src/js/components/ActivityLog/actions.tsx +++ b/projects/packages/activity-log/src/js/components/ActivityLog/actions.tsx @@ -26,11 +26,17 @@ type UseActivityActionsOptions = { /** * Row actions for the DataViews table. The single primary action deep- - * links into the Calypso Backup restore flow for the row's rewind point - * (`https://cloud.jetpack.com/backup/{slug}/restore/{rewindId}`) and opens - * in a new tab. Eligibility requires `activityIsRewindable`, a - * `rewindId`, and a `calypsoSlug` from Initial_State; rows missing any - * of those don't render the action. + * links into the Jetpack Cloud Backup restore flow for the row's rewind + * point (`https://cloud.jetpack.com/backup/{slug}/restore/{rewindId}`) + * and opens in a new tab. Eligibility requires `activityIsRewindable`, + * a `rewindId`, and a `calypsoSlug` from Initial_State; rows missing + * any of those don't render the action. + * + * TEMPORARY: this off-site link is a stop-gap until the Backup wp-admin + * port (https://github.com/Automattic/jetpack/pull/48236) lands. Once + * that ships, every row action here should point at the in-admin + * Backup page instead of cloud.jetpack.com so users stay inside their + * own wp-admin for the restore flow. * * @param options - Hook options. * @param options.isLoading - Whether the list is currently fetching. Kept @@ -46,7 +52,7 @@ export function useActivityActions( { const backupAction: Action< Activity > = { id: 'backup', isPrimary: true, - label: __( 'Manage backup', 'jetpack-activity-log' ), + label: __( 'Restore backup', 'jetpack-activity-log' ), icon: , isEligible: item => Boolean( item.activityIsRewindable && item.rewindId && calypsoSlug ), callback: async items => { @@ -57,7 +63,7 @@ export function useActivityActions( { const url = `https://cloud.jetpack.com/backup/${ encodeURIComponent( calypsoSlug ) }/restore/${ encodeURIComponent( item.rewindId ) }`; - tracks?.recordEvent( 'jetpack_activity_log_manage_backup_click', { + tracks?.recordEvent( 'jetpack_activity_log_restore_backup_click', { rewind_id: item.rewindId, activity_name: item.activityName, } );