Skip to content

Backup: wire the modernized dashboard to real REST + Gates - #48889

Open
dhasilva wants to merge 11 commits into
trunkfrom
update/backup-modernization-data-layer
Open

Backup: wire the modernized dashboard to real REST + Gates#48889
dhasilva wants to merge 11 commits into
trunkfrom
update/backup-modernization-data-layer

Conversation

@dhasilva

@dhasilva dhasilva commented May 16, 2026

Copy link
Copy Markdown
Contributor

Fixes # n/a

Proposed changes

  • Wire the modernized VaultPress Backup dashboard (PR Backup: modernized dashboard UI — Overview, Restore, Download (mocked) #48853, behind rsm_jetpack_ui_modernization_backup) to real REST endpoints. The mocked-data scaffold from Phase 1 is replaced with TanStack Query 5 hooks talking to a thin set of PHP REST bridges (Capabilities, Activity_Log, File_Browser, Download, Restore) under projects/packages/backup/src/rest/. The legacy dashboard remains byte-identical when the modernization filter is off.
  • <Gates> mounts inside <DashboardLayout> between the QueryClientProvider and the body: top-to-bottom loading → not-connected → secondary-admin → no-plan → children. Connection state is read from window.JP_CONNECTION_INITIAL_STATE, emitted inline by Jetpack_Backup::render_connection_initial_state on admin_print_scripts priority 1 — the modernized enqueue path short-circuits the legacy Connection_Initial_State::render_script() call, so without this <Gates> would sit on its loading skeleton forever. We avoid pulling in @automattic/jetpack-connection because its barrel imports SCSS wp-build can't resolve cleanly.
  • Activity list (Overview) drives DataViews from useActivityLog( { page, pageSize } ). Paging is server-side: the /jetpack/v4/site/rewindable-activity bridge forwards number and page to WPCOM, and the envelope's totalItems / totalPages feed DataViews' paginationInfo — the same pattern the trunk jetpack-activity-log package uses against the parent /sites/{id}/activity endpoint. keepPreviousData keeps the current page on screen while the next one is in flight instead of flashing a spinner over the list. The right pane resolves its row via useActivityById, which subscribes to the same page query the list uses and falls through to any other cached pages of the family before returning the "not found" state; useDefaultBackupRewindId subscribes to page 1 so first load preselects the newest backup the moment that page resolves (deduped with the list's own fetch when it's on page 1 at the default per-page).
  • File browser uses useFileTree. WPCOM's /rewind/backup/ls actually returns contents as a map keyed by filename, not an array. The real folder/leaf discriminator is has_children, not type — inside /wp-content, folders like languages and mu-plugins are reported as type: 'file' with has_children: true. The virtual type: 'wordpress' core-version markers are filtered out.
  • File preview uses useFileContents. VaultPress addresses file blobs by the per-entry period — the timestamp when that file last changed — not the parent backup's rewindId. The bridge query param is named file_period accordingly, with a long docblock capturing the failure mode if it's reverted (silently signs a URL for a non-existent snapshot, then 400 "File not found" on stream). FileNodeFile carries period and the volume-prefixed manifestPath (e.g. f5:/wp-config.php) so the card has everything it needs without a separate metadata call.
  • Capabilities_Bridge proxies WPCOM's /sites/{id}/rewind/capabilities — the dedicated capabilities endpoint, same one the legacy plugin already uses. The previously-considered /sites/{id}/rewind?force=wpcom returned a state-shaped response whose capabilities key was missing on multi-product plans (Jetpack Complete), producing a false "no plan" gate.
  • Restore + Download screens use useRestore / useDownload (TanStack useMutation to kick off + polled useQuery for status) with the same idle → submitting → progress → success | error state machine the Phase 1 mocks defined. The Restore bridge signs against rest/v1 /activity-log/{site}/rewind/to/{rewindId} as_user — blog-token auth is rejected by WPCOM with That API call is not allowed for this account; comment in the bridge captures the original investigation. Both screens derive their displayed timestamp directly from the rewindId (Unix-seconds), so they don't depend on Overview having populated its cache first.
  • No more file metadata fetch: WPCOM's /sites/{id}/rewind/backup/path-info returns "No file found" for every input variant we synthesized and has no working caller anywhere in the monorepo. lastModified comes from /ls's period and mime type is inferred from the file extension instead; the unused endpoint + hook + tests are dropped.
  • Stats display: item.stats now reads from entry.content.text ("44 plugins, 23 themes, 1562 uploads, 99 posts, 18 pages") instead of entry.object.backup_stats (a stringified JSON blob in production that was getting rendered verbatim).
  • Drop the ?jpb-mock=1 developer affordance and the dev-mode banner now that everything is real. Removes use-is-mock-mode, the four use-mock-* hooks, the dev-mode-banner component, the three fixture files, and the <DevModeBanner /> mount in DashboardLayout.
  • Tests: Jest covers toIntRewindId (3 cases) + normalize/activity-log.ts (6 cases); phpunit smoke-tests each bridge — confirms routes only register behind the modernization filter and reject subscriber-level access with 403.

Related product discussion/links

Does this pull request change what data or activity we track or use?

No.

Testing instructions

  1. Check out this branch, run pnpm install from the repo root, then composer install in projects/packages/backup/. Build with jp build packages/backup --deps — on a fresh checkout a bare pnpm run build inside the package fails, because wp-build resolves @automattic/jetpack-components from its build/ output, so js-packages/components (and in turn js-packages/boost-score-api, js-packages/number-formatters, js-packages/social-logos) plus packages/wp-build-polyfills must be built first.
  2. Enable the modernization filter via mu-plugin:
    <?php
    add_filter( 'rsm_jetpack_ui_modernization_backup', '__return_true' );
  3. Visit wp-admin → Jetpack → VaultPress Backup.
  4. On a real backup-plan'd site: confirm the activity list populates from WPCOM, clicking a backup row loads the file browser, expanding a folder issues a real /jetpack/v4/rewind/backup/ls call, clicking a text file (e.g. readme.html) opens the FileInfoCard with Modified (from /ls period), Type (inferred from the extension), and a monospace preview body (≤ 64 KB).
    • Note: some files may show "Preview unavailable for this file." even when their extension is text-shaped. This happens when the file's period predates available content blobs (VaultPress retains manifest entries longer than blob storage) — it's expected, not a regression.
  5. On a site WITHOUT a backup plan: confirm <Gates> renders the "no plan" fallback with a CTA to jetpack.com/upgrade/backup/. Tested on a fully-connected Jetpack Complete site too — should pass through to the body since Complete includes Backup.
  6. On a disconnected site: confirm <Gates> renders the "Connect Jetpack" fallback.
  7. Click "Download backup" → submit → progress → success surfaces the signed download URL. Click "Restore to this point" → submit → progress polls every 1.5 s → success or error notice.
  8. Toggle the modernization filter OFF (rename the mu-plugin to .off, reload). Confirm the legacy Backup admin renders unchanged.

🤖 Generated with Claude Code

@github-actions

github-actions Bot commented May 16, 2026

Copy link
Copy Markdown
Contributor

Are you an Automattician? Please test your changes on all WordPress.com environments to help mitigate accidental explosions.

  • To test on WoA, go to the Plugins menu on a WoA dev site. Click on the "Upload" button and follow the upgrade flow to be able to upload, install, and activate the Jetpack Beta plugin. Once the plugin is active, go to Jetpack > Jetpack Beta, select your plugin (Jetpack or WordPress.com Site Helper), and enable the update/backup-modernization-data-layer branch.
  • To test on Simple, run the following command on your sandbox:
bin/jetpack-downloader test jetpack update/backup-modernization-data-layer
bin/jetpack-downloader test jetpack-mu-wpcom-plugin update/backup-modernization-data-layer

Interested in more tips and information?

  • In your local development environment, use the jetpack rsync command to sync your changes to a WoA dev blog.
  • Read more about our development workflow here: PCYsg-eg0-p2
  • Figure out when your changes will be shipped to customers here: PCYsg-eg5-p2

@github-actions github-actions Bot added [Package] Backup [Plugin] Backup A plugin that allows users to save every change and get back online quickly with one-click restores. [Plugin] Jetpack Issues about the Jetpack plugin. https://wordpress.org/plugins/jetpack/ [Tests] Includes Tests labels May 16, 2026
@github-actions

github-actions Bot commented May 16, 2026

Copy link
Copy Markdown
Contributor

Thank you for your PR!

When contributing to Jetpack, we have a few suggestions that can help us test and review your patch:

  • ✅ Include a description of your PR changes.
  • ✅ Add a "[Status]" label (In Progress, Needs Review, ...).
  • ✅ Add testing instructions.
  • ✅ Specify whether this PR includes any changes to data or privacy.
  • ✅ Add changelog entries to affected projects

This comment will be updated as you work on your PR and make changes. If you think that some of those checks are not needed for your PR, please explain why you think so. Thanks for cooperation 🤖


Follow this PR Review Process:

  1. Ensure all required checks appearing at the bottom of this PR are passing.
  2. Make sure to test your changes on all platforms that it applies to. You're responsible for the quality of the code you ship.
  3. You can use GitHub's Reviewers functionality to request a review.
  4. When it's reviewed and merged, you will be pinged in Slack to deploy the changes to WordPress.com simple once the build is done.

If you have questions about anything, reach out in #jetpack-developers for guidance!


Jetpack plugin:

No scheduled milestone found for this plugin.

If you have any questions about the release process, please ask in the #jetpack-releases channel on Slack.


Backup plugin:

No scheduled milestone found for this plugin.

If you have any questions about the release process, please ask in the #jetpack-releases channel on Slack.

@jp-launch-control

jp-launch-control Bot commented May 16, 2026

Copy link
Copy Markdown

Code Coverage Summary

Coverage changed in 6 files. Only the first 5 are listed here.

File Coverage Δ% Δ Uncovered
projects/packages/backup/src/dashboard/components/file-info-card/index.tsx 0/20 (0.00%) 0.00% 12 💔
projects/packages/backup/src/class-jetpack-backup.php 77/443 (17.38%) -0.20% 5 💔
projects/packages/backup/src/dashboard/screens/download.tsx 0/10 (0.00%) 0.00% 4 💔
projects/packages/backup/src/dashboard/screens/restore.tsx 0/10 (0.00%) 0.00% 4 💔
projects/packages/backup/src/dashboard/components/file-browser/index.tsx 0/151 (0.00%) 0.00% 1 ❤️‍🩹

27 files are newly checked for coverage. Only the first 5 are listed here.

File Coverage
projects/packages/backup/src/dashboard/components/gates/index.tsx 0/11 (0.00%) 💔
projects/packages/backup/src/dashboard/components/gates/no-backup-plan.tsx 0/2 (0.00%) 💔
projects/packages/backup/src/dashboard/components/gates/not-connected.tsx 0/2 (0.00%) 💔
projects/packages/backup/src/dashboard/components/gates/secondary-admin.tsx 0/2 (0.00%) 💔
projects/packages/backup/src/dashboard/data/api/activity-log.ts 0/1 (0.00%) 💔

Full summary · PHP report · JS report

If appropriate, add one of these labels to override the failing coverage check: Covered by non-unit tests Use to ignore the Code coverage requirement check when E2Es or other non-unit tests cover the code Coverage tests to be added later Use to ignore the Code coverage requirement check when tests will be added in a follow-up PR I don't care about code coverage for this PR Use this label to ignore the check for insufficient code coveage.

@github-actions

Copy link
Copy Markdown
Contributor

This PR has been marked as stale. This happened because:

  • It has been inactive for the past 3 months.
  • It hasn't been labeled `[Pri] BLOCKER`, `[Pri] High`, `[Status] Keep Open`, etc.

If this PR is still useful, please do a [trunk merge or rebase](https://github.com/Automattic/jetpack/blob/trunk/docs/git-workflow.md#keeping-your-branch-up-to-date) and otherwise make sure it's up to date and has clear testing instructions. You may also want to ping possible reviewers in case they've forgotten about it. Please close this PR if you think it's not valid anymore — if you do, please add a brief explanation.

If the PR is not updated (or at least commented on) in another month, it will be automatically closed.

dhasilva and others added 11 commits August 4, 2026 19:26
Adds the @tanstack/react-query infrastructure with no behavior change.
DashboardLayout now wraps children in a QueryClientProvider so every
screen in every route shares a module-scope singleton client — caches
survive navigation between routes.

Phase 2 setup commit. Capability gates and real REST hooks follow.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Introduces the per-feature REST bridges pattern with a thin
Rest_Controller that registers routes only when the modernization
filter is on. Capabilities_Bridge proxies /sites/{id}/rewind to
expose hasBackupPlan + planSlug for the React layer.

Gates mounts inside DashboardLayout between the QueryClientProvider
and the body slot. Top-to-bottom: loading → not-connected →
secondary-admin → no-plan → children. All three routes inherit
gating for free.

Connection state comes from window.JP_CONNECTION_INITIAL_STATE,
emitted inline on the modernized Backup admin page from
class-jetpack-backup.php (the modernized enqueue path skips the
legacy render_script call, so we need to put the global up ourselves).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replaces useMockActivityLog with useActivityLog backed by an
apiFetch -> bridge -> WPCOM /sites/{id}/activity/rewindable chain.
A small normalize/activity-log.ts maps WPCOM's rewindable-activity
shape (gridicon, content.text, object.backup_stats) onto the
ActivityItem discriminated union the UI already consumes.

The hook returns `totalItems` in addition to `totalPages` because the
trunk ActivityList drives DataViews' `paginationInfo` from both. The
WPCOM endpoint doesn't accept `page` or `search`, so the hook fetches
a 100-item window and filters/paginates client-side — DataViews
remains the single source of truth for the visible slice.

Overview's right-pane lookup swaps from the fixture's
findActivityById to getCachedActivityById, which reads the same
single-window cache the list populates. The default selection
(newest backup) is derived from the cache the same way.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds the File_Browser_Bridge (ls + path-info + file-content), each
proxying WPCOM /sites/{id}/rewind/backup/* via as_user. The file-content
endpoint resolves the signed stream URL server-side and caps the
returned body at 64 KB so previews can't balloon the REST response.

React side: useFileTree replaces useMockFileTree, useFileContents
replaces the static findContents() fixture lookup. The rewindId prop
on FileBrowser is now actually consumed and threaded through every
NodeRow so each folder fetches its own children.

FileInfoCard no longer reads metadata from fixture nodes — usePathInfo
fetches size + mime + last-modified for the open file, and
useFileContents pulls the preview body only when the mime type is
text-shaped. FileNodeFile's metadata fields drop to optional since
WPCOM's /ls endpoint only returns name + type.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
useDownload replaces useMockDownload, driving the same
idle → submitting → progress → success | error state machine off
a TanStack useMutation (initiate) + polled useQuery (status).
The bridge proxies WPCOM's /rewind/backups/{rewindId}/prepare-download
and /rewind/backups/{rewindId}/downloads/{id} endpoints.

The screen now derives its "download point" timestamp directly from
the rewindId (it's unix-seconds) instead of resolving it through the
activity-log fixture — Download / Restore can stand alone without
relying on Overview having populated its cache first.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
useRestore replaces useMockRestore, driving the same state machine off
a TanStack useMutation (initiate) + polled useQuery (status). The
bridge proxies WPCOM's v1 /activity-log/{site}/rewind/to/{rewindId}
and /rewind/{restoreId}/restore-status endpoints, with the verbatim
comment explaining why the rewind endpoint must sign as_user
(blog-token auth is rejected by WPCOM with "That API call is not
allowed for this account").

Same rewindId → unix-seconds derivation as the Download screen, so
Restore stands alone without depending on Overview's cache.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Removes the last mock-mode artifacts: useIsMockMode, the dev banner
component, the four mock hooks, the three fixture files, and the
<DevModeBanner /> mount inside DashboardLayout. The modernized
Backup dashboard now only renders real data via the jetpack/v4/*
bridges, with <Gates> handling the not-connected and no-plan
fallbacks.

This completes Phase 2 of the Backup modernization.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
One test class per bridge:
- Rest_Capabilities_Bridge_Test (2 tests, 2 assertions)
- Rest_Activity_Log_Bridge_Test (2 tests, 2 assertions)
- Rest_File_Browser_Bridge_Test (4 tests, 4 assertions)
- Rest_Download_Bridge_Test (3 tests, 4 assertions)
- Rest_Restore_Bridge_Test (3 tests, 4 assertions)

Each class confirms the routes only register when the modernization
filter is on and that the manage_options permission_check rejects
subscriber-level access with a 403. Upstream WPCOM calls aren't
exercised here — they need network and a real connection.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
REST bridges:
- Centralize the not-connected check via Rest_Controller::get_blog_id_or_error
  so every bridge short-circuits with a 412 instead of building /sites/0/…
- Require a user-level WPCOM connection in Rest_Controller::permission_check,
  matching the jetpack-activity-log package's pattern
- Rename Activity_Log_Bridge's route from /jetpack/v4/activity-log →
  /jetpack/v4/site/rewindable-activity to avoid colliding with the
  jetpack-activity-log package, which owns that route inside the Jetpack plugin
- Cap File_Browser_Bridge's preview proxy at the HTTP transport layer
  (limit_response_size) so multi-GB blobs can't be buffered into PHP memory
  before truncation, and validate the signed URL with wp_http_validate_url
- Drop the raw upstream WPCOM body from Restore_Bridge's WP_Error envelope —
  the React layer only reads err.message

React layer:
- Convert getCachedActivityById / getCachedDefaultBackupRewindId to
  useQuery-backed hooks so Overview's default selection reconciles to the
  newest backup the moment the activity-log window resolves
- Collapse the activityLog cache key to a single shared activityLogWindow
  key the hook always uses
- Drop the raw error_code fallback in useRestore — never surface
  machine identifiers like "checksum_mismatch" to users
- Refresh stale doc comments (DashboardLayout no longer mentions the
  dev-mode banner; FileBrowser references useFileTree instead of
  useMockFileTree)

Changelog:
- Align the Jetpack plugin entry with Phase 1: Type: other for the
  flag-gated rollout

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…es + review cleanup

The RestoreItemsChecklist selection was collected into local state on the
Restore and Download screens but never sent to the bridge, so an unchecked
"Plugins" or "Themes" was silently restored anyway. Thread `RestoreItems`
through `useRestore` / `useDownload` → `initiateRestore` / `initiateDownload`
as the mutation variable, and surface it to the bridges as `data: { types }`
(the keys already match WPCOM's `types` payload).

Also clears the review suggestions from the same pass:

* `useFileContents` browser path now UTF-8 encodes via TextEncoder before
  btoa, so non-ASCII manifest paths (accented or CJK upload filenames)
  no longer throw InvalidCharacterError.
* `useConnection` drops its empty-deps useMemo and reads
  JP_CONNECTION_INITIAL_STATE on every render — single property lookup,
  removes any risk of capturing a pre-emit empty snapshot if load order
  ever shifts.
* `query-client.ts` docstring corrected: each wp-build route is its own
  bundle so the cache is per-route, not shared across navigation. The
  design doesn't depend on cross-route reuse, but the comment shouldn't
  claim otherwise.
* `Restore_Bridge::initiate_restore` returns the same user-facing
  "Could not start the backup restore." string in both failure branches
  instead of the diagnostic "Restore response missing restore id."
* `Activity_Log_Bridge` drops the declared-but-unused `aggregate`,
  `after`, `before` args (and mirrors the cleanup in
  `data/api/activity-log.ts`'s FetchArgs) — the hook only ever passes
  `number`.
* Download success link gains `download` + `rel="noreferrer"` so the
  signed URL triggers a download instead of navigating away when WPCOM
  omits Content-Disposition.

PHP + JS tests pass; phan unchanged (only the pre-existing build/ artifact
warning); eslint clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Five fixes raised in /jetpack-review-pr feedback:

* Activity list pagination is now real server-side pagination instead
  of a 100-item client-side window. The bridge forwards `number` and
  `page` to WPCOM; `useActivityLog` uses `keepPreviousData` for smooth
  page transitions and reads `totalItems`/`totalPages` straight from
  the envelope. The cache key is per-page (`activityLogPage(page,
  pageSize)`); `activityLogRoot()` is the family prefix for cross-page
  lookup. Overview lifts view state so `<RightPane>`'s
  `useActivityById` subscribes to the same paginated query, falling
  through to other cached pages via `getQueriesData` if the row was
  loaded on a different page. The 100-item cap was a relic of the
  mocked-data phase. DataViews `search` is disabled — WPCOM's
  `/rewindable` endpoint doesn't accept a search param and the
  rewindable list is homogeneous enough that hand-rolling client-side
  search wasn't worth it.
* `useConnection` no longer spins forever when
  `JP_CONNECTION_INITIAL_STATE` is missing. The "any key present"
  loaded heuristic is replaced with always-loaded + a dev-mode
  console warning, so `<Gates>` falls through to
  `<NotConnectedScreen>` instead of staying on the skeleton.
* `<FileInfoCard>` surfaces `useFileContents` errors with a distinct
  muted message ("Preview could not be loaded for this file. It may
  no longer be available in storage."). The mime-mismatch branch
  keeps its generic "Preview unavailable" copy. Drops the inert
  per-file Download/Restore buttons — per-file actions aren't on
  this phase's roadmap and ghost affordances are worse than no
  affordance.
* `useRestore` and `useDownload` surface `statusQuery.error` as a
  terminal `error` phase ("Lost connection while restoring." /
  "Lost connection while preparing download.") instead of leaving
  the UI stuck on the last known progress percent with no message
  and no way out — important for Restore in particular, which is
  irreversible.

Tests + lint + tsc clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@dhasilva
dhasilva force-pushed the update/backup-modernization-data-layer branch from 34ea3b9 to c332789 Compare August 4, 2026 22:33
@dhasilva
dhasilva marked this pull request as ready for review August 4, 2026 23:26
@dhasilva dhasilva added [Status] Needs Review This PR is ready for review. and removed [Status] In Progress labels Aug 4, 2026
@dhasilva

dhasilva commented Aug 5, 2026

Copy link
Copy Markdown
Contributor Author

Code review — thorough pass (3,954 lines, 3 projects)

Verdict: minor issues — no blockers found. The PHP side is well-guarded (capability + user-connection permission checks, flag-gated route registration, careful proxying of WPCOM-signed URLs); everything below is a frontend error-state or consistency issue.

Tests were run locally from the PR head: jp test php packages/backup ✅, jp test js packages/backup ✅, jp phan packages/backup ✅ (0 issues, no new suppressions, no baseline growth).

Findings

  1. A failed capabilities fetch renders the "no plan" upsell on entitled sites. gates/index.tsx#L28-L46useCapabilities exposes error but <Gates> never reads it. A transient WPCOM 5xx or network failure on a site with a Backup plan falls through to ! capabilities.data?.hasBackupPlan → "This site doesn't have an active Backup plan" plus an upgrade CTA. Add an error branch (retry affordance or generic error screen). Related: the loading gate checks capabilities.isLoading before the synchronous connection check, so a disconnected site spins through a doomed request (plus its retry: 1) before showing the not-connected screen — reordering the !isFullyConnected check above the loading gate fixes both the wait and one of the error paths.

  2. Rows with unmapped gridicons are silently dropped while server-side pagination still counts them. normalize/activity-log.ts#L22-L24GRIDICON_TO_KIND knows five gridicons, but WPCOM's rewindable feed spans more event families (users, comments, settings, deletions — e.g. cross, user, trash). Dropped entries make pages render sparse (a "10 per page" page showing 6 rows) while totalItems / totalPages from the envelope still count them, and a bookmarked ?selected= pointing at a dropped row can never resolve. Consider mapping unknown gridicons to a generic kind with a fallback icon instead of returning null.

  3. File-browser fetch failures render as an empty tree / "Empty" with no feedback. file-browser/index.tsx#L325useFileTree returns error, but FileBrowser ignores it for the root listing (error → children null → roots = [] → blank tree) and NodeRow shows the "Empty" label when a folder's /ls call fails. Same for a 200 response with ok: false. Surface an error row/notice so a broken proxy isn't indistinguishable from an empty backup.

  4. .js and .xml files get a mime type the preview logic then rejects. file-info-card/index.tsx#L25-L35 vs #L128-L135EXT_TO_MIME maps js → application/javascript and xml → application/xml, but isTextual() accepts neither, so those files show "Type: application/javascript" alongside "Preview unavailable for this file." Add both to isTextual() (leaving image/svg+xml excluded seems deliberate and fine).

  5. keys.fileContents( rewindId, path ) — the first argument is actually the file's period. query-client.ts#L45useFileContents calls it with the file's own snapshot period. The PR itself goes to great lengths (bridge docblock, hook docblock) to warn that period ≠ rewindId; this parameter name re-plants exactly that confusion for the next reader. Rename to filePeriod.

  6. Branding: "Add Jetpack Backup…" inside a dashboard branded "VaultPress Backup". no-backup-plan.tsx#L22DashboardLayout sets PRODUCT_NAME = 'VaultPress Backup'; the no-plan copy should use the same product name.

Test coverage gaps

  1. No test asserts routes are absent when the modernization filter is off — the legacy-no-op guarantee the PR description says is tested. One assertion in any bridge test (e.g. next to Rest_Capabilities_Bridge_Test.php#L70) closes it.

  2. The user_not_connected branch of permission_check (admin who isn't WPCOM-linked) and the response projections (capabilities → hasBackupPlan, restore-status field mapping) are untested; the latter two are mockable via the pre_http_request filter under WorDBless.

Checked and clear

  • Security: capability-based gating on every route (subscriber → 403 covered by tests); user inputs schema-typed by REST core and rawurlencoded into WPCOM paths; the file-content proxy validates the WPCOM-returned signed URL with wp_http_validate_url and caps the body via limit_response_size at the transport layer; the inline connection-state script echoes Initial_State::render(), which JSON-encodes with JSON_HEX_TAG | JSON_HEX_AMP — no </script> breakout.
  • Backward compat / version skew: is_modernized() private → public is safe; every cross-package symbol used comes from automattic/jetpack-connection, a required dep (incl. Jetpack_Options from its legacy/), so the version-picking autoloader can't serve an older copy; the new REST classes ship in the same package as their caller.
  • Route collisions: checked the whole monorepo for the seven new jetpack/v4 routes — none collide; the deliberate avoidance of the activity-log package's namespace is documented in the bridge.
  • Feature gating: routes, UI, and the inline script all sit behind the filter (+ page check for the script, printed before print_head_scripts at priority 20 so it lands before any module executes).
  • Changelog entries (valid Comment:-only entries for all three projects), i18n, CSS logical properties, PHP 7.2 syntax, dependency declarations, no leftover jpb-mock references, connectionStatus key names verified against REST_Connector::connection_status().
  • One thing I could not independently verify: download status treats WPCOM progress as a 0–1 fraction (* 100) while restore treats percent as 0–100 — there's no other consumer of the wpcom/v2 prepare-download endpoints in the monorepo to compare against, but the live test in the PR description (step 7) would have made a unit mismatch obvious.

Generated by Claude.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

[Package] Backup [Plugin] Backup A plugin that allows users to save every change and get back online quickly with one-click restores. [Plugin] Jetpack Issues about the Jetpack plugin. https://wordpress.org/plugins/jetpack/ [Status] Needs Review This PR is ready for review. [Tests] Includes Tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant