Tags: Automattic/jetpack
Tags
Activity Log: Port AL into wp-admin as a native page (#48244) * Activity Log: Phase 0 package scaffold Introduce a new projects/packages/activity-log/ package mirroring the Backup package's structure, and wire it into the main Jetpack plugin's late_initialization() so it registers its admin page and REST namespace on every request. Phase 0 only lays the foundation: a placeholder Admin component renders at admin.php?page=jetpack-activity-log, the jetpack/v4/activity-log REST namespace is reserved (no handlers yet), and the menu item is gated the same way the existing my-jetpack "Activity Log ↗" item is (connected user + non-multisite + manage_options). The old my-jetpack Cloud-redirect menu item is intentionally left in place for this phase — Phase 1 removes it. Refs #48242. * Activity Log: Phase 1 menu swap Retire the legacy "Activity Log ↗" Cloud-redirect item that my-jetpack was registering. The Phase 0 package now solely owns the menu entry at position 14; no more duplicate entries, no more ↗ arrow. Changes: - Delete projects/packages/my-jetpack/src/class-activitylog.php and its test. Remove the Activitylog::init() call from the package's Initializer. The class had one job (register an external-redirect menu item) and that job is gone. - On WPCOM hosts, jetpack-mu-wpcom's wpcom-admin-menu now hides the new jetpack-activity-log slug instead of the now-defunct cloud-activity-log-wp-menu redirect URL, preserving the existing WPCOM behavior where wordpress.com/activity-log/<domain> is the active link on those sites. Self-hosted users: single "Activity Log" item, routes to the local (placeholder) admin page. WPCOM-hosted users: single "Activity Log" item, routes to wordpress.com/activity-log (unchanged from before this PR). Refs #48242. * Activity Log: Phase 2 REST surface Replace the Phase 0 REST_Controller stub with real handlers backing the two endpoints the Calypso Dashboard Activity Log UI actually calls: GET /jetpack/v4/activity-log GET /jetpack/v4/activity-log/count/group Both are thin proxies to wpcom/v2 /sites/{blog_id}/activity[/count/group] via Client::wpcom_json_api_request_as_blog(), and they return the raw WPCOM response shape so the ported @automattic/api-core fetcher in Phase 3 keeps its existing `response.current.orderedItems → activityLogs` transform unchanged. Permission callback is `manage_options`, matching the menu gate. The Calypso UI has no single-event lookup, so this phase does not add /activity-log/{id} — a trimmed scope from the original plan in #48242. Accepted query params match Calypso's ActivityLogParams 1:1: number, page, sort_order, after, before, group[], not_group[], text_search (plus the group-counts subset). Unknown params are dropped server-side, so the UI can forward its filter state verbatim. Refs #48242. * Activity Log: Sign REST proxies as the user, not the blog The Phase 2 endpoints were signing with the blog token via `wpcom_json_api_request_as_blog()`, and WPCOM's `/sites/{id}/activity` rejects that with "Only Administrators can query information about the current site." — the upstream endpoint needs to know *which* admin is asking. Switch to `wpcom_json_api_request_as_user()` (matching the existing `/jetpack/v4/site/activity` proxy in `class.core-rest-api-endpoints.php:2041`), pass API version `'2'`, and forward the visitor IP via `X-Forwarded-For` for parity. Also tighten the permission callback: admins without a user-level WPCOM connection now get a clear 403 with `activity_log_user_not_connected` instead of a confusing forwarded "Only Administrators…" error. Refs #48242. * Activity Log: Phase 3 — port the DataViews UI Ported Calypso Dashboard's `client/dashboard/sites/logs-activity/dataviews/` into the activity-log package. The admin page now renders a real table wired to the Phase 2 REST endpoints via `@wordpress/api-fetch`: - DataViews table (search, activity-type filter, sort, pagination) - ActivityActor + ActivityEvent cell renderers (avatar, gridicon→WP icon map, activity title + formatted description) - Simplified FormattedBlock renderer: text decorators (strong/em/pre/ filepath) + Link when `range.url` is present. Entity renderers (post/comment/person/plugin/theme/backup) render children-only because those Calypso routes don't resolve inside wp-admin. - Local TanStack Query factories (activityLogQuery, activityLogGroupCountsQuery) that mirror the Calypso api-core fetcher shapes (including the `current.orderedItems → activityLogs` unwrap), so future ports stay aligned. - DataViews stylesheet imported via Sass (it's a bundled WP package in jetpack-webpack-config, not externalized — same pattern as `projects/packages/forms/routes/shared.scss`). - `jetpack-admin-page-layout` mixin scoped to the Activity Log body class. - Initial_State seeds `gmtOffset`, `timezoneString`, `slug`, `locale` for date-cell formatting. Deliberate scope simplifications vs. Calypso (each tracked in the PR body): - No date range picker (was a sibling control in the parent logs shell). - No URL-persistent view state. - No analytics. - No tier gating / upsell → Phase 4. - Backup row action stubbed (disabled) → Phase 5. Refs #48242. * Activity Log: Polish — AdminPage header, full-bleed table, 32px icons Feedback-driven polish on the Phase 3 UI: - Wrap the page in `<AdminPage>` from `@automattic/jetpack-components` so the Jetpack masthead + footer + viewport-pinned scroll column come for free. Same pattern SEO (#48154) and the new Backup overview (#48236) adopted. - Drop the 24px inset around the DataViews table — it now runs full-bleed inside AdminPage's content column. The wrapper keeps a flex-column layout so DataViews' internal scroll continues to work under `jetpack-admin-page-layout`. - Lock the event-icon tile to a fixed 32x32 (box-sizing: border-box, explicit width/height/min-width + 4px padding over the 24px SVG). Matches Calypso's visual spec and stays stable against surrounding cascade. Column widths were already in views.ts via DataViews' documented `layout.styles` per-field object — same mechanism Calypso uses. Refs #48242. * Activity Log: Tweak event-icon fill, hide footer, tighten columns Follow-up polish from design feedback on Phase 3: - Event-icon fill → #757575. - AdminPage `showFooter={ false }`: the full-bleed DataViews page doesn't need Jetpack's footer eating vertical space. - Column minimum widths in views.ts: published, published_utc: 200 actor: 100 event: 520 (Dropped the maxWidth pairs — columns flex above these minima.) Refs #48242. * Activity Log: Widen date and event minimum column widths published / published_utc → 240px (was 200) event → 580px (was 520) Refs #48242. * Activity Log: Let the Event column absorb remaining width Give event `width: 100%` alongside its minWidth. Classic HTML-table trick: 100%-width column consumes leftover space when siblings have fixed widths, so Event now stretches to fill the viewport while Date and User stay at their minima. Refs #48242. * Activity Log: Use the real Jetpack logo for the "Jetpack" actor When the actor is "Jetpack" / "Jetpack Boost" / Happiness Engineer, the avatar was falling back to the WordPress icon. Swap in JetpackLogo from @automattic/jetpack-components (already a dep) so those rows show the proper brand. Refs #48242. * Activity Log: wire Reset view + persist view options Rename the broken `onResetView` prop to DataViews' actual `onReset` with the `false | function` semantics so the built-in Reset view button renders in the cog popover (disabled until the view is modified, indicator dot on the cog when modified). Extract the view state into a `usePersistentView` hook that mirrors Calypso's behavior: persist the non-transient bits (fields, density, sort, perPage, layout) to `localStorage` under `jetpack-activity-log:view`, strip `page`/`search`/empty `filters` before writing, and clear the entry when the view returns to default. The hook's interface leaves room to swap the backing store to user-meta later without touching the component. Refs #48242. * Activity Log: document the UI primitives preference order Add a package-level AGENTS.md that steers future UI work toward the WordPress Design System in the right order: `@wordpress/ui` first, `@wordpress/components` only where `@wordpress/ui` has no stable equivalent, `@wordpress/dataviews` sub-components for data-presentation extensions, and `@wordpress/admin-ui` (via `AdminPage`) for page layout. Also flags the `@wordpress/design-system-mcp` server as the canonical lookup path for component/token metadata. Guidance follows the April 2026 WordPress Design System P2 post. Refs #48242. * Activity Log: adopt --wpds-* design tokens in SCSS Swap the remaining hard-coded hex values in activity-actor.scss and activity-event.scss for WordPress Design System semantic tokens, keeping the original hex as each `var()` fallback so rendering stays identical when the tokens aren't in scope: - `#dcdcde`, `#f0f0f1` → `--wpds-color-bg-surface-neutral-weak` - `#1e1e1e` → `--wpds-color-fg-content-neutral` - `#50575e`, `#757575` → `--wpds-color-fg-content-neutral-weak` The `@wordpress/dataviews` bundled stylesheet already declares the wpds tokens at :root, so the values take effect immediately rather than waiting on a separate `@wordpress/theme` provider. Expect a small shift: the actor icon bubble becomes slightly lighter (#dcdcde → #f4f4f4) and the two variants of medium grey unify on #707070. Primary text (#1e1e1e) and event-icon background (#f0f0f1 → #f4f4f4) are visually unchanged. Refs #48242. * Activity Log: link event-description entities to wp-admin screens The parser already produces typed entity tokens (post, person, comment, plugin, theme) with the IDs and slugs needed to deep-link into wp-admin. Wire each one to its matching core screen so users can jump straight from a log row to the edited object: - post → post.php?post={id}&action=edit - person → user-edit.php?user_id={id} - comment → comment.php?action=editcomment&c={id} - plugin → plugins.php?s={slug} - theme → themes.php?theme={slug} The admin URL prefix comes from the Initial_State `siteData.adminUrl` value so non-standard admin paths (subdirectory installs, custom `admin_url` filters) are respected instead of hard-coding `/wp-admin/`. Entities with no wp-admin analog (site, backup) stay plain-strong. Actor-column linking would need a `wp_user_id` in the activity-log API actor payload and is out of scope here. Refs #48242. * Activity Log: route entity tokens to wp-admin, not wordpress.com Two fixes on top of the entity-link pass: 1. The WPCOM activity-log API wraps typed entity ranges (post, person, plugin, theme, comment) in an outer anchor whose href points at the `https://wordpress.com/…` equivalent. That outer `Link` was winning and sending users to wordpress.com before the inner `EntityLink` could emit the local wp-admin URL. Match Calypso's Jetpack Cloud guard: if the anchor URL is a wordpress.com URL, drop it and render children only so the inner renderer takes over. This is what makes post entities link to `post.php?post=…` and user entities stop escaping to wordpress.com. 2. Drop the `<strong>` wrapper from entity rendering (both linked and unlinked paths) — entity names now render at the same weight as surrounding prose; the anchor alone signals interactivity. Refs #48242. * Activity Log: link entities that don't surface as typed ranges Real payloads from /jetpack/v4/activity-log expose entity identity three ways. The previous pass only handled typed ranges (type: 'post'/'person'/…); this adds the other two: Section-tagged anchor ranges. The login event's content.ranges is a single range with type: 'a', url pointing at wordpress.com, and `section: 'user'` + `id: 1`. The parser now preserves `id` and `site_id` on link nodes, `buildAdminLink` accepts link/a nodes and routes by `section`, and the Link renderer tries the local link before its wordpress.com-drop guard. Net effect: "keoshi successfully logged in…" now links the username to user-edit.php. Entry-level object fallback. post__published events return no content.ranges at all — the post title lives only in the top-level `object: { type: 'Article', object_id, name }`. Activity now carries `activityObject` end-to-end, a new `buildObjectAdminLink` maps Article→post.php and Person→user-edit.php, and ActivityEvent wraps the whole description in a link when there's a linkable object and the description has no ranges of its own. Refs #48242. * Activity Log: Phase 4 — tier gating and upsell Gate the full Activity Log behind a paid Backup-enabled plan. Mirrors Calypso's gating (free tier: last 20 events only, no search/filters/ sort/pagination) while adding a real server-side cap so the limit can't be bypassed from DevTools. Server-side: - `REST_Controller::has_activity_logs_access()` calls WPCOM's `/sites/{id}/rewind` endpoint (same signal Jetpack_Backup uses) and caches the boolean in a site transient for 5 minutes, keyed on blog_id. Fail-closed (no access) on error, with a short 1-min cache so transient WPCOM hiccups don't hammer the endpoint. - `get_activity_log` clamps `number` to 20 and forces `page=1` when access is false, regardless of what the caller sent. `wp.apiFetch` from the console can't page past the free-tier boundary. - `Initial_State.siteData.hasActivityLogsAccess` exposes the same boolean to the React bundle so the UI starts in the right state on page load. Client-side: - Read `hasActivityLogsAccess` from the initial state; when false, force `config.perPageSizes = [ 20 ]` on DataViews, zero out `paginationInfo.totalPages`, and replace the default UI via `children={<DataViews.Layout />}` (hides search, filters, sort, view-config). Same switches as Calypso at wp-calypso:client/dashboard/sites/logs-activity/dataviews/ index.tsx:201-208. - `UpsellCallout` renders beneath the table when gated and `logData` is non-empty. Title, body copy, and the illustration SVG are a 1:1 port of Calypso's ActivityLogsCallout. CTA flows through Jetpack's standard `useProductCheckoutWorkflow` with `productSlug: 'jetpack_security_t1_yearly'` (Security bundle unlocks 30 days of history per cloud.jetpack.com/features/comparison) and `from: 'activity-log-page-purchase'` as the checkout source. Closes Phase 4 of #48242. Refs #48242. * Activity Log: retarget upsell callout copy at the wp-admin context Two Calypso-origin strings didn't translate to the self-hosted Jetpack upgrade flow we actually route to: - Title "Track every action with Jetpack Activity" → "…with Jetpack Activity Log". Matches the product name in wp-admin menus and this package's own i18n domain; "Jetpack Activity" read as truncated. - "Available on WordPress.com paid plans." → "Available on the Jetpack Security and Complete plans." The CTA already goes to `jetpack_security_t1_yearly`; the plan-availability line now names the Jetpack bundles that actually unlock Activity Log (per cloud.jetpack.com/features/comparison). Refs #48242. * Activity Log: drop the access cache on return from checkout Pattern borrowed verbatim from Jetpack Social (`Publicize\Social_Admin_Page::admin_init`): hand WordPress.com a `redirect_to` that carries `refresh_access=1` + a `_wpnonce`, and on page load the admin-init hook verifies the nonce and calls `REST_Controller::clear_access_cache()` before `Initial_State::get_data()` runs. The Initial_State re-seeds `hasActivityLogsAccess` with a fresh WPCOM fetch, so a just-upgraded site stops showing the upsell callout the moment the browser lands back on the Activity Log page — no more waiting out the 5-minute transient. - `Jetpack_Activity_Log::REFRESH_ACCESS_NONCE_ACTION` constant + `admin_init` nonce check (silent `wp_verify_nonce` instead of `check_admin_referer`'s die-on-failure so a stale bookmark doesn't show an error page). - `Initial_State` seeds the nonce under `nonces.refreshAccess`. - `UpsellCallout` passes `redirectUrl = currentUrl + ?refresh_access=1 &_wpnonce=<nonce>` to `useProductCheckoutWorkflow` via `@wordpress/url`'s `addQueryArgs`. The cache only gets cleared on upgrade, not on downgrade — a removed plan still takes up to 5 minutes to reflect. Downgrades are rare enough and don't hurt as much as the upgrade-lag did. Refs #48242. * Activity Log: stop pre-marking the view as modified on load `isViewModified` used to fall out of a full-view `fastDeepEqual`. When DataViews' first render normalized anything inside `view.layout` the compare flipped even though the user hadn't touched a thing, so the Reset view button was enabled (and the cog carried a dot) on a cold page load. Narrow the compare to a signature of the fields the settings-cog actually exposes — sort, order, properties, density, items-per-page, filters. Everything else (DataViews layout subfields, future internal additions) is ignored for the modified-ness decision. Storage still writes the full stripped view, so restoration keeps its full shape. Also self-heal on mount: if an earlier session wrote a "not-really-modified" entry to localStorage (pre-fix), drop it and boot from the default view. Refs #48242. * Activity Log: add a date-range picker above the table 1:1 port of Calypso's `components/date-range-picker/` — Dropdown toggle with a calendar-icon button, popover with a preset sidebar (Today, Yesterday, Last 7/30 days, Month/Year to date, Last 12 months, Last 3 years, Custom), dual-month calendar (collapses to one on mobile), and start/end InputControls. Five TS files + one SCSS + a slim local `datetime.ts` lifted from Calypso's utils for self-contained helpers. New deps: `@automattic/ui` (for `DateRangeCalendar` + `TZDate`) and `date-fns` — both already resolved in the monorepo `node_modules`. Wiring: `dateRange` is client-only state (no localStorage) defaulting to last-7-days anchored at the site's calendar today, not the browser's. The range threads into the REST list params (`after` / `before`) and the group-counts query so filter dropdown totals stay in sync with the displayed window. `end` stretches to 23:59:59.999 before ISO-encoding so single-day ranges like "Today" aren't empty. Changing the range snaps pagination to page 1 (same convention as perPage/sort/filters/search already use in `onChangeView`). Only renders when `hasActivityLogsAccess` is true — on the free tier the row is hidden, matching the rest of the Phase 4 gating. Package-level `eslint.config.mjs` relaxes a few rules for the ported directory (`jsdoc/require-description`, `react/jsx-no-bind`, and `@wordpress/no-unused-vars-before-return`) so upstream re-syncs stay mechanical rather than requiring a JSDoc audit on every refresh. Refs #48242. * Activity Log: move date-range picker into the AdminPage header Two polish fixes for the picker landed in the previous commit: 1. Move it into the page header via the admin-ui `actions` slot (AdminPage threads `actions` straight into `@wordpress/admin-ui`'s `Page`), so the picker sits alongside the title/subtitle instead of floating on its own row above the table. Matches MSD's layout for the logs pages. 2. Import `@automattic/ui/style.css`. The JS bundle for `@automattic/ui`'s `DateRangeCalendar` doesn't carry its own styles — the published package ships them as a separate entry in its `exports` map. Without it the calendar cells render with wp-admin's default button styling (each day is a boxed button outline); with it we get the clean text-only numbers that Calypso and MSD both show. Refs #48242. * Activity Log: add tracks events for user interactions Wires `@automattic/jetpack-analytics` (already the canonical tracker for Jetpack wp-admin packages — Backup, Search, Publicize, etc.) into the Activity Log page so product / growth can see which affordances people actually use. Small local `use-analytics` hook initializes the tracker with the connected WPCOM user identity, same pattern as `projects/packages/backup/src/js/hooks/useAnalytics.js`. Seven events, all namespaced `jetpack_activity_log_*`: Calypso parity (same breakdown as wp-calypso:client/dashboard/sites/ logs-activity/dataviews/index.tsx:146-174): - `per_page_changed` — `{ per_page }` - `filter_changed` — `{ num_groups_selected, num_total_activities_selected, group_<id>: bool }` - `search` — `{ has_query }` (boolean so the query text never leaks) - `page_changed` — `{ page }` New for this port: - `date_range_changed` — `{ days_in_range }` (sidecar date picker) - `reset_view_click` — `{}` (wraps DataViews' onReset) - `upsell_cta_click` — `{ source: 'free_tier_callout' }` (paid-plan CTA) Deferred: sort change (Calypso doesn't track), entity-link clicks in the description (too noisy), Phase 5 restore-point click (blocked on #48236). Also refreshes the stale header comment in ActivityLog/index.tsx so it reflects the current scope-simplifications set (drops "no date range picker", "no analytics events"; keeps localStorage, unlinked actor, disabled backup action). Refs #48242. * Activity Log: fix CI — stub CHANGELOG.md, changelog/.gitkeep, TS error Three tightly-related CI fails on #48244 and #48264: 1. `Changelogger validity` — \`projects/packages/activity-log/CHANGELOG.md\` didn't exist. Seeded an initial file pointing at the 0.1.0-alpha pre-release version this branch is cutting. 2. `Project structure` — lint requires each package's \`changelog/\` folder to carry a \`.gitkeep\` so Release doesn't remove the directory when the queue is empty. 3. `Type checking` — tsgo types `__()`'s return as a branded `TransformedText<…>`, which made the later `actorName = name || actorName` reassignment fail type-check. Annotate the variable as `string` so the widening happens at declaration. (`Build all projects` fail was the same TS error cascading through the plugin compile; it clears with this fix.) Refs #48242. * Activity Log: stop the prod i18n check flagging @automattic/ui strings The `DateRangeCalendar` component we pull in from `@automattic/ui` calls `__('Date calendar')` / `__('%s, selected')` / etc. without a textdomain argument. Dev builds don't care; the `Build all projects` CI job runs the production build, which trips `i18n-check-webpack-plugin`'s strict validation and fails with seven `domain argument (index 2) is missing` errors. Same recipe the `@wordpress/*` bundled packages use in `jetpack-webpack-config`'s `BundledWpPkgsTranspileRules`: run the `@automattic/babel-plugin-replace-textdomain` babel plugin over `@automattic/ui` source at bundle time so every bare `__()` gets our `jetpack-activity-log` textdomain wired in. Verified locally with `NODE_ENV=production BABEL_ENV=production pnpm run build-client` — clean compile. Refs #48242. * Activity Log: address engineering-review findings from #48244 Three real fixes + two documentation passes in response to @CGastrell's review. 1. Drop `staleTime: Infinity` on the TanStack client (src/js/index.js). The activity log is append-only; an infinitely-stale cache meant new events landing upstream while the page stayed open never surfaced. Also reproduced the "Last 7 days missing today's event, Last 30 days includes it" symptom: the Last-7 query was cached before the event existed, Last-30 re-fetched after, and flipping back served the pre-event snapshot. 60s + `refetchOnWindowFocus` strikes the right balance. 2. Default `view.page` to 1 in `listParams` so the key is stable across the initial render → first-user-interaction transition (`view.page` goes `undefined` → `1`). Eliminates one WPCOM round- trip per page load. 3. Strip `filters` unconditionally in `stripTransient` — previously only empty-array filters were dropped, so a "Plugins" selection survived across reloads even though the PR described filters as transient. Matches `search`'s existing behavior. Plus three comment-only improvements flagged in the nitpicks list: - Free-tier clamp in `get_activity_log()` documents that the in-place `set_param` is deliberate (security property: no downstream filter can undo the cap). - `get_activity_log_group_counts()` explains why counts are deliberately *not* tier-clamped (they're cosmetic, and stable full-history counts keep the filter dropdown from flickering as users type). - `Initial_State::get_data()` notes the synchronous WPCOM call on cache miss and its ~200ms–2s render-blocking window. - `use-activity-log.ts::buildPath` notes that `apiFetch` / `@wordpress/url` re-serialize arrays as indexed brackets (`group[0]=plugin`) on the wire — both forms round-trip through the REST controller. Refs #48242, review in #48244. * Activity Log: offer DataViews' Activity layout alongside Table `@wordpress/dataviews@14.1.0` ships an Activity timeline layout as a first-class option alongside Table / List / Grid (see the Gutenberg storybook story `dataviews-dataviews--layout-activity`). For an append-only event log, the timeline shape reads more naturally than a dense table — so add it as a toggle rather than a replacement. Users flip between the two via the layout switcher in the cog popover. Minimal wiring for the first pass: - `defaultLayouts.activity.titleField: 'event'` — reuses the existing composite `ActivityEvent` render (icon + title + formatted description stacked) as the timeline item's title block. - Leaves `mediaField` / `descriptionField` unset. The layout falls back to its default bullet + stacks Date/User below as "other fields" with visually-hidden labels. - Date, User, and filter state (activity type) stay unchanged — the cog still exposes them, both layouts render them. Visual polish (splitting the event-icon into a dedicated mediaField, moving the date into a dedicated descriptionField, etc.) is a follow- up once this lands and we can compare side-by-side. Refs #48242. * Activity Log: wire the Activity layout's media/title/description slots The first pass reused the composite `event` field as both `view.titleField` *and* one of the `view.fields`, which made DataViews render the event body twice (once in the title block, once as an "other fields" row) and kept the gridicon trapped inside the title stack rather than in the layout's left-hand media slot. Fix: split `ActivityEvent` into three exported sub-components (`ActivityEventIcon`, `ActivityEventTitle`, `ActivityEventDescription`), expose each as its own DataViews field (`event_icon`, `event_title`, `event_description`), and update `defaultLayouts.activity` so those three sit in `mediaField` / `titleField` / `descriptionField` respectively while `view.fields` lists only Date + User (rendered as the subdued metadata row below the title). Table layout keeps the composite `event` field unchanged. DataViews' `getHideableFields` helper already excludes the three slot-bound fields from the Properties toggle list, so the cog popover still shows a clean Title / Media / Description lock group on top of the regular Date / User toggles. Refs #48242. * Activity Log: address layout-review feedback from #48244 Four related fixes, all introduced by the commit that added the Activity layout toggle (DataViews' titleField/mediaField/descriptionField slots): - Table titles stopped being dark. The event-title class sat on a <span> wrapper, which matched the .site-activity-logs__event-content > span { color: grey } rule meant for the description. Move the class onto the <strong> itself so the selector only catches the description's span. - Activity-layout icons weren't perfect circles. Our .site-activity-logs__event-icon class sets min-width: 32px, which beat DataViews' width: 100% rule (25px at balanced density) and produced a 32x25 rounded-rect instead of a circle. Scope the tile styling to the Table layout's composite .site-activity-logs__event wrapper; in the Activity layout the same <ActivityEventIcon> is rendered standalone and now picks up DataViews' own circular bullet styling. - Columns broke after Activity -> Table. DataViews' layout switcher does onChangeView({ ...view, type, ...defaultLayouts[type] }), so any key *not* present in the target layout's defaults carries over. Activity sets titleField/mediaField/descriptionField, and the Table layout separately renders a composite "primary column" when those are set, producing a ghost Title column before the regular Event column. Move default layouts into ./views.ts and explicitly set those slot refs (and groupBy) to undefined on the Table variant so the spread clears them. Also re-declare the Table's column-width styles in the default so the switcher restores them (it deletes view.layout on every switch). - Day grouping in the Activity timeline. Add a hidden published_date field that returns the formatted calendar day in the site's timezone, and wire groupBy: { field: 'published_date' } into the Activity defaults. Events on the same day now collapse under a single "April 24, 2026" header. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Activity Log: center the Activity-layout meta row and anchor the actor right DataViews renders the per-item "other fields" row as a flex container with the default `align-items: stretch`, which left-aligns the date text to the top of the cell while the actor's HStack (icon + name) sits vertically centered — producing a visible baseline mismatch in the screenshot-shared feedback. Scope an `align-items: center` onto `.dataviews-view-activity__item-fields` so every cell shares a single midline, and push the trailing cell (actor) to the far right with `margin-inline-start: auto`. Keeping the rule inside `.jp-activity-log__dataviews-wrapper` so it doesn't leak to any other DataViews instance in the wp-admin shell. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Activity Log: track layout toggles between Table and Activity Layout switches flow through the same DataViews onChangeView callback as every other view mutation, but until now we only fired events for the per-page / filter / search / page / date-range / reset cases. The Jetpack port is the only Activity Log surface that exposes the DataViews Activity layout alongside the default Table, so add a `jetpack_activity_log_layout_changed` event (with the resolved `next.type` as the `layout` prop) so we can tell which layout users actually stick with. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Activity Log: address package-structure review feedback from #48244 Four follow-ups from @anomiex's review of the package layout, plus a nitpick from @CGastrell's earlier walkthrough. 1. Drop the `V0001` namespace suffix on the three package PHP classes and the consuming `use` in the Jetpack plugin. The `VXXX` pattern was lifted from Backup, where it grew out of an autoloader-version incident (see peaFOp-2ar-p2). For a brand-new package with no incident history yet, the suffix just adds friction. Package_Version stays unsuffixed for the same reason it does in Backup — the `jetpack_package_versions` filter expects to find the class at a stable name across upgrades. 2. Replace the autoloaded top-level `actions.php` with an `add_filter()` call inside `Jetpack_Activity_Log::initialize()`. Matches the videopress / connection / search / sync / stats convention. The composer `autoload.files` entry is dropped along with the file. 3. Site-scope the localStorage key in `usePersistentView`. The single `'jetpack-activity-log:view'` key meant an admin who manages multiple Jetpack-connected sites in the same browser shared one view across all of them; key now includes the WPCOM blog ID seeded by Initial_State and falls back to `default` when the global is absent (storybook/tests). 4. Delete the `.babelrc`. The package has no Jest tests today (the follow-up tests PR #48264 only adds PHPUnit), so the `@babel/preset-env` config it carried wasn't being consumed. `babel.config.js` continues to drive the webpack build. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Activity Log: default to the Activity timeline layout Flip DEFAULT_VIEW from Table → Activity so the page boots into DataViews' built-in timeline (grouped by day, icon + title + description in a left rail) instead of the table. Table is still available from the cog popover's layout switcher. The Activity layout is closer to the "every change in one searchable timeline" framing in the page subtitle and matches the pattern users see in WordPress.com's logs surfaces. Users with persisted view state keep what they had — only fresh loads pick up the new default. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Activity Log: hide the cog's "modified" indicator dot DataViews renders a small dot on the view-options cog whenever the current view differs from the default (via our `onReset` prop). The same signal also gates the "Reset view" button inside the cog popover, which is enough on its own — the dot just adds visual noise on a page where toggling sort order, density, or filters is routine. Hide the indicator via SCSS scoped to our wrapper. The reset functionality is unchanged. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Activity Log: tooltip the disabled "Manage backup" action The Phase 5 stub renders "Manage backup" as a disabled primary action so its column space stays committed and the planned feature is discoverable. Without any hint, users see a dead button and have to guess why. DataViews' `Action` type has no tooltip prop and the action button is rendered by the library, not us — so attach a native `title` attribute via a small effect rooted on the dataviews wrapper. The matching is keyed on the localized "Manage backup" label so other disabled actions (if any land later) don't get the same hint by accident. A MutationObserver re-applies the title after pagination or filter changes when DataViews replaces the row DOM. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Activity Log: collapse empty description rows DataViews' Activity layout always renders the descriptionField slot wrapper, even when our `ActivityEventDescription` returns `null` for events without description text (e.g. "User removed", which has only a title). The empty wrapper still participates in the column stack's gap, leaving a visible blank line under the title. Hide it via `:empty` so the row collapses tightly. Scoped to the Activity Log wrapper so we don't disturb other DataViews instances. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Activity Log: collapse changelog entries to single end-user-facing notes Per anomiex review on #48244 — package changelog dirs aren't a commit log; consolidate the 30 iteration entries into one initial release note, and rewrite the jetpack-plugin entry to describe the feature instead of the package wiring. * admin-page-layout mixin: extend flex chain into AdminPage's Container/Col `<AdminPage>`'s title-branch wraps children in `<Container fluid horizontalSpacing={0}><Col>{children}</Col></Container>`, which is `display: grid` plus a content-sized grid cell. The mixin's `flex: 1 1 auto; overflow: auto` already bounded Container, but the chain broke at Col — any inner `flex: 1 1 auto; min-height: 0` on a consumer's wrapper was inert under a non-flex parent. DataViews-style pages (Activity Log) ended up letting their content grow to its natural size and the outer Container scrolled the whole thing instead of letting DataViews's own `.dataviews-layout__container` scroll the table body. Force the outer Container/Col pair to flex column so consumers can fill their bounded slot. Form-style pages (My Jetpack, Newsletter, Social) keep working: their content stays content-sized via default `flex: 0 1 auto` and Container's `overflow: auto` still catches anything taller than the slot. Verified on a Jurassic Ninja site at 1440x900 and 1440x500 across: Activity Log (DataViews internal scroll now lives at .dataviews-layout__container, header + toolbar pinned), My Jetpack, Social, Newsletter, Forms — no regressions. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Activity Log: harden the free-tier server-side gate Two follow-ups from @douglas's review of #48244 to projects/packages/activity-log/src/class-rest-controller.php. 1. Free-tier bypass via filter inputs. The previous clamp limited only `number` and `page` — `after`/`before`/`text_search`/`group`/ `not_group` were forwarded to WPCOM verbatim. A free-tier caller via DevTools could date-walk the entire history 20 rows at a time by resetting `before` to the timestamp of the oldest visible event, or full-text-search the full log via `text_search`. Now `get_activity_log()` nulls out everything outside `FREE_TIER_ALLOWED_PARAMS` (`number` / `page` / `sort_order`) before delegating to `proxy_get`, so the response is bounded to "the 20 most recent events overall" — the same dataset the locked-down UI shows. 2. Failure-cache TTL of 60s downgraded paying admins for too long. A one-second WPCOM blip persisted "no" for a full minute; long enough that an admin who refreshed during the window saw the full free-tier UI flip-flop. Cut to 10s — still enough to keep a flapping endpoint from getting hammered, short enough that paid customers recover almost immediately. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Activity Log: use esc_url_raw for the JSON-bound adminUrl `esc_url()` is for HTML-attribute contexts — it escapes `&` to `&`. The `adminUrl` value flows through `wp_json_encode()` and into JS that concatenates it into hrefs, so an HTML-escaped ampersand would survive into the final URL. Today `admin_url()` rarely contains `&` so the bug is invisible, but the inconsistency with the sibling `WP_API_root` (which already uses `esc_url_raw()`) is the kind of thing that bites later. Drop the now-unused import while we're here. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Activity Log: tidy two React hook side-effects Two small follow-ups from review: * `usePersistentView` was calling `writePersistedView( null )` from inside the `useState` lazy initializer for the self-heal branch. That's a side effect during render — React 18 strict-mode double- invokes lazy initializers and would write twice. Move the cleanup into a one-shot mount `useEffect`; the lazy initializer now only picks the right starting view. * `useAnalytics` re-called `jetpackAnalytics.initialize()` every time any consumer's effect re-fired with the same identity. Guard with a module-level `identifiedFor` flag so the singleton is only re- identified when the (id, login) pair actually changes. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Activity Log: mark the Manage-backup tooltip hack as #48236 follow-up The `MutationObserver` that retro-fits a `title="Coming soon"` onto the disabled "Manage backup" row action is a stopgap until #48236 lands — at which point the action becomes a real enabled link and the entire effect goes away. Add a TODO with the issue reference so the next reader knows it's intentionally temporary, not a pattern to extend. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Activity Log: align tanstack range; bump my-jetpack changelog Two metadata fix-ups from review: * `@tanstack/react-query` was pinned to `5.90.8` in the new package's package.json while existing siblings (backup, my-jetpack, mu-wpcom) use `^5.15.5`. Both ranges are satisfied by 5.90.8 today, but a fixed pin on a fast-moving lib while siblings carry a caret risks future split resolutions. Switch to `^5.90.8` — the resolver still picks 5.90.8 (no downgrade), and now every consumer's range narrows to the same minimum. * Bump the my-jetpack changelog entry from `patch` to `minor`. The removal drops the public `Automattic\Jetpack\My_Jetpack\Activitylog` class — even though only the package's own `Initializer` calls it in-tree, it's part of the package's exported namespace, so a consumer outside the monorepo could in principle have wired `Activitylog::init()` into their own boot path. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * AdminPage: add `unwrapped` prop for full-bleed pages Adds an opt-in `unwrapped` prop to `<AdminPage>` (default false). When true, children render directly inside admin-ui's `<Page>` instead of inside the default `<Container fluid horizontalSpacing={0}><Col>{children}</Col></Container>` wrap. Use for full-bleed surfaces (DataViews-based admin pages, full-app dashboards) that own their own scroll/layout model and don't want the outer Container's grid layout to break their flex chain. Activity Log opts in: with `unwrapped`, `.admin-ui-page` directly contains the wrapper, the mixin's `overflow: auto` slot lands on the wrapper, and DataViews's own `.dataviews-layout__container` handles internal scroll for the table body — header, date picker, and DataViews toolbar stay pinned on short viewports. Verified on a Jurassic Ninja site at 1440x900 and 1440x500. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Co-authored-by: ilonagl <ilona.jaudzemyte@automattic.com> Co-authored-by: Christian Gastrell <cgastrell@gmail.com> Co-authored-by: Douglas <douglas.henri@automattic.com>
PreviousNext