You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Replace the legacy custom-component VideoPress admin dashboard (currently rooted at projects/packages/videopress/src/client/admin/) with a modern wp-build app under projects/packages/videopress/routes/dashboard/. The new dashboard covers four screens — Overview, Library, Video details, and Settings — and is built with @wordpress/components / @wordpress/ui, @wordpress/dataviews (for the Library), and @automattic/charts (for the Overview). No custom components except for video-domain pieces with no upstream equivalent (VideoThumbnail with timecode overlay, VideoPlayer) and Jetpack shell (AdminPage, JetpackFooter, ConnectionError).
All work hides behind the rsm_jetpack_ui_modernization_videopress filter (projects/packages/videopress/src/class-admin-ui.php). The legacy app keeps shipping until the cutover phase flips the filter default and removes the old code.
Approach
UI-first with mocked data, many small PRs. Each screen ships first as a UI-only PR with hardcoded fixture data and no-op handlers, so designers can check out the PR, enable the filter, and click around to validate the design without backend coupling. Wiring to real APIs lands in later PRs. The feature flag makes incremental, trunk-merging PRs safe.
Plan
Phase 0 — Feature flag + wp-build scaffold. Modernization filter rsm_jetpack_ui_modernization_videopress, routes/dashboard/{route,stage}.tsx stubs, wp-build-polyfills, branched menu render callback, build/watch pipeline integration. Landed in #48494.
Phase 1 — Shared chrome + Settings tab.AdminPage header, JetpackFooter, top-level tabs (Overview / Library / Settings) via wp-build sub-routes (routes/overview/, routes/library/, routes/settings/) wrapped in a shared DashboardLayout. Settings tab UI fully built in this phase (single "Restrict video access" toggle) — toggle state held in local React state with no persistence, validating the chrome end-to-end without backend coupling. Real persistence wires up in Phase 6. Other tabs stay placeholders. JITM slot (<div id="jp-admin-notices">) intentionally not ported — same as the Forms dashboard. Initial-state seeding deferred to Phase 5 (no client-side consumer in Phase 1). Also added sub-path exports ./admin-page and ./jetpack-footer to @automattic/jetpack-components to bypass dirty-SCSS in the package barrel under wp-build's esbuild + sass-plugin. Landed in VideoPress: Modernization Phase 1 — Shared chrome + Settings tab #48510.
Phase 2 — Library UI (mocked). DataViews-based video listing with @wordpress/dataviews. Hardcoded fixture array of mock videos. Bulk actions, search, filter, sort, pagination, view-mode toggle, and column/density config all driven by DataViews built-ins; handlers are no-ops at this stage. Upload button opens a stub modal. Floating bulk-action bar comes from DataViews' bulk-actions API. This is the largest UI PR. Landed in VideoPress: Modernization Phase 2 — Library tab (DataViews grid + table) #48586.
Phase 3 — Overview UI (mocked). KPI cards (Views / Visitors / Watch time, with delta indicators via @automattic/chartsTrendIndicator), LineChart for the views-trends graph with comparison series, BarListChart (or LeaderboardChart) for "Most viewed", GeoChart + table for "Top locations". All driven by hardcoded fixture data. Date range picker: before implementing, verify whether@automattic/ui's DateRangeCalendar is published and usable directly; only fall back to porting Activity Log's picker (projects/packages/activity-log/src/js/components/DateRangePicker/) if not.
Open in VideoPress: Modernization Phase 3 — Overview (mocked) #48682.
Note (2026-05-11): Phase 7 discovery surfaced after this PR opened (see "Phase 7 discovery outcome" below). The two discovery-driven swaps — Visitors KPI → Impressions and Top locations card → Top videos by watch time — were folded into VideoPress: Modernization Phase 3 — Overview (mocked) #48682 directly (the UI-only PR is still pre-review, so the swaps land here rather than as a vestigial follow-up) along with the mock-data tuning needed to make the new metrics make sense.
Phase 4 — Video details UI (mocked). Form using @wordpress/components (InputControl, TextareaControl, ToggleControl, RadioControl, Modal). Breadcrumb header replacing the existing back-button. ⋯ menu in the header with Download file / Delete actions (Download file is new — wire the no-op stub). Card-based layout grouping (Privacy & sharing, Rating). Chapters helper modal preserved (rebuilt on @wordpress/componentsModal if not already there); description stays a plain TextareaControl. Shortcode is kept (not in mocks but preserved to avoid functionality loss); rendered using the same WP primitives as the "Link to video" copy field. Open in VideoPress: Modernization Phase 4 — Video details (mocked) #48669.
Phase 5 — Initial_State + REST surface for existing endpoints. Seed class-initial-state.php with API root, nonce, and minimal capability flags (originally scoped into Phase 1 but deferred since Phase 1 had no client-side consumer). PHP REST controller (class-rest-controller.php) wrapping the existing endpoints used by the legacy dashboard: videopress/v1/settings, /wp/v2/media (filtered to videopress mime), /wpcom/v2/videopress/meta, /wpcom/v2/videopress/playback-jwt, videopress/v1/site, videopress/v1/features. TanStack Query hooks added alongside.
Phase 6 — Wire Library + Video details + Settings to real data. Replace fixture imports with TanStack Query hooks across the three screens that share the Phase 5 REST surface. Single PR.
Phase 7 — Discover & implement video-stats REST proxy. Identify WPCOM endpoint(s) for views, visitors, watch time, top videos, and top countries; document shape. Implement /jetpack/v4/videopress/stats/* proxies on the Activity Log pattern (user-signed Client::wpcom_json_api_request_as_user). May spawn a sub-issue if endpoint discovery requires WPCOM-side work.
Note (2026-05-11): Discovery complete — proxy scope reduces to stats/video-plays?complete_stats=true (current + previous period). The geo and unique-visitor pieces of the original scope are storage-level gaps on WPCOM and have been turned into deferred follow-ups (see "Phase 7 discovery outcome" below). The UI swaps have already landed in VideoPress: Modernization Phase 3 — Overview (mocked) #48682. Phase 7 is now backend-only and is being combined with Phase 5 in a single PR on branch update/videopress-modernization-phase-7 (stacked on VideoPress: Modernization Phase 3 — Overview (mocked) #48682) — both phases share the REST controller / Initial State scaffolding, so landing them together avoids two passes over the same files.
Phase 8 — Wire Overview to real data. Replace fixture data sets with TanStack Query hooks against the new stats proxy. Date-range picker threads into after/before query params.
Note (2026-05-11): Remains a separate PR landing after Phases 5+7. With the UI swaps already in VideoPress: Modernization Phase 3 — Overview (mocked) #48682 and the proxy + Initial State scaffolding planned for the combined Phase 5+7 PR, this phase reduces to the JS hook swap (replace useMockStats with a TanStack Query hook against the new proxy) plus threading the date-range picker into the proxy's query params.
Phase 9 — Cutover. Flip rsm_jetpack_ui_modernization_videopress default to true. Delete the legacy app at projects/packages/videopress/src/client/admin/, the legacy webpack entry, and the legacy enqueue branch in class-admin-ui.php. The wp-build path becomes the only path.
Phase 7 discovery outcome (2026-05-11)
The original Phase 3/7/8 plan above is preserved as-written so the rationale for the change is traceable. This section documents what the WPCOM-side investigation surfaced and the resulting design adjustments.
WPCOM-side investigation found two data gaps that change the Overview design relative to the mocks:
Per-video geography is not collected. The video_stats table has no country / region columns, and stats/country-views + stats/location-views/{period} are site-wide (no post_id filter). There is no way to build a per-video "Top locations" card from existing data — this is a storage-level gap, not just a missing endpoint, and would require new collection + aggregation on the WPCOM side (a larger Stats Team project). Decision: the "Top locations" card is replaced by "Top videos by watch time", which reuses the same RankingCard and surfaces engagement rather than reach.
Unique-visitor counts aren't recoverable. The video_stats table doesn't store viewer IDs or session keys, and stats/video-plays?complete_stats=true returns views, impressions, and watch_time per day — none of which is a unique-visitor count. Decision: the "Visitors" KPI is renamed to "Impressions". Impressions is real, returned by the same endpoint, and is a coherent reach metric distinct from views (which require a play).
The two-column bottom layout and three-card KPI row are preserved; only the metric in two slots changes.
Deferred follow-ups from this discovery (do not block Phase 7)
WPCOM-side: per-video country/region collection. Would unblock a real per-video "Top locations" card.
WPCOM-side: top-embed-pages aggregation over the page_url column already present in video_stats. Would surface a real geography-shaped insight without new collection.
Free-tier UX. VideoPress free tier allows a single upload (different shape from Activity Log's view-20 limit). The mocks don't include a free-tier state. Awaiting designer input. Once defined, expect to add an UpsellCallout analogous to Activity Log's, gated server-side in the REST proxy and UI-side via DataViews configuration.
Storage meter placement.VideoStorageMeter is not in the mocks. Map current display conditions in the legacy dashboard, then take to designers to decide on placement (Overview sidebar? Settings? Drop entirely?).
ConnectionError verification. During scoping, both trigger paths failed to render the component (PHP Error_Handler::report_error and JS wp.data.dispatch('jetpack-connection').setConnectionErrors). Likely a real bug in projects/js-packages/connection/hooks/use-connection-error-notice/. File a separate issue; ensure the new chrome renders the component once fixed.
Branch & PRs
Phase 0 landed in #48494 on branch update/videopress-modernization. Subsequent phases each open their own PR directly to trunk; the modernization filter keeps the in-progress dashboard hidden in production. Each phase's PR ticks the box above when merged.
Deferred follow-ups
phpunit + jest test scaffolding for the new package surfaces.
Analytics tracking events for new screens (parity with useAnalyticsTracks in the legacy app, plus new events for the Overview's KPI/chart interactions).
Out of scope
Calypso source porting — there is no Calypso source in this repo. The mocks and @automattic/charts are the design references; this is a from-scratch rebuild on Gutenberg primitives, not a port.
Multi-site / agency flows.
Net-new features beyond modernization parity (chapters editor UI, video transcripts, custom thumbnails beyond what exists today, etc.).
Replace the legacy custom-component VideoPress admin dashboard (currently rooted at
projects/packages/videopress/src/client/admin/) with a modern wp-build app underprojects/packages/videopress/routes/dashboard/. The new dashboard covers four screens — Overview, Library, Video details, and Settings — and is built with@wordpress/components/@wordpress/ui,@wordpress/dataviews(for the Library), and@automattic/charts(for the Overview). No custom components except for video-domain pieces with no upstream equivalent (VideoThumbnailwith timecode overlay,VideoPlayer) and Jetpack shell (AdminPage,JetpackFooter,ConnectionError).Architecture mirrors
projects/packages/activity-log/— TanStack Query (@tanstack/react-query),class-initial-state.phpbootstrap, REST controller proxying user-signed WPCOM requests, DataViews-driven listing,useProductCheckoutWorkflowfor upsell. Reference: #48242.All work hides behind the
rsm_jetpack_ui_modernization_videopressfilter (projects/packages/videopress/src/class-admin-ui.php). The legacy app keeps shipping until the cutover phase flips the filter default and removes the old code.Approach
UI-first with mocked data, many small PRs. Each screen ships first as a UI-only PR with hardcoded fixture data and no-op handlers, so designers can check out the PR, enable the filter, and click around to validate the design without backend coupling. Wiring to real APIs lands in later PRs. The feature flag makes incremental, trunk-merging PRs safe.
Plan
rsm_jetpack_ui_modernization_videopress,routes/dashboard/{route,stage}.tsxstubs,wp-build-polyfills, branched menu render callback, build/watch pipeline integration. Landed in #48494.AdminPageheader,JetpackFooter, top-level tabs (Overview / Library / Settings) via wp-build sub-routes (routes/overview/,routes/library/,routes/settings/) wrapped in a sharedDashboardLayout. Settings tab UI fully built in this phase (single "Restrict video access" toggle) — toggle state held in local React state with no persistence, validating the chrome end-to-end without backend coupling. Real persistence wires up in Phase 6. Other tabs stay placeholders. JITM slot (<div id="jp-admin-notices">) intentionally not ported — same as the Forms dashboard. Initial-state seeding deferred to Phase 5 (no client-side consumer in Phase 1). Also added sub-path exports./admin-pageand./jetpack-footerto@automattic/jetpack-componentsto bypass dirty-SCSS in the package barrel under wp-build's esbuild + sass-plugin. Landed in VideoPress: Modernization Phase 1 — Shared chrome + Settings tab #48510.@wordpress/dataviews. Hardcoded fixture array of mock videos. Bulk actions, search, filter, sort, pagination, view-mode toggle, and column/density config all driven by DataViews built-ins; handlers are no-ops at this stage. Upload button opens a stub modal. Floating bulk-action bar comes from DataViews' bulk-actions API. This is the largest UI PR. Landed in VideoPress: Modernization Phase 2 — Library tab (DataViews grid + table) #48586.@automattic/chartsTrendIndicator),LineChartfor the views-trends graph with comparison series,BarListChart(orLeaderboardChart) for "Most viewed",GeoChart+ table for "Top locations". All driven by hardcoded fixture data. Date range picker: before implementing, verify whether@automattic/ui'sDateRangeCalendaris published and usable directly; only fall back to porting Activity Log's picker (projects/packages/activity-log/src/js/components/DateRangePicker/) if not.Open in VideoPress: Modernization Phase 3 — Overview (mocked) #48682.
@wordpress/components(InputControl,TextareaControl,ToggleControl,RadioControl,Modal). Breadcrumb header replacing the existing back-button. ⋯ menu in the header with Download file / Delete actions (Download file is new — wire the no-op stub). Card-based layout grouping (Privacy & sharing, Rating). Chapters helper modal preserved (rebuilt on@wordpress/componentsModalif not already there); description stays a plainTextareaControl. Shortcode is kept (not in mocks but preserved to avoid functionality loss); rendered using the same WP primitives as the "Link to video" copy field. Open in VideoPress: Modernization Phase 4 — Video details (mocked) #48669.class-initial-state.phpwith API root, nonce, and minimal capability flags (originally scoped into Phase 1 but deferred since Phase 1 had no client-side consumer). PHP REST controller (class-rest-controller.php) wrapping the existing endpoints used by the legacy dashboard:videopress/v1/settings,/wp/v2/media(filtered to videopress mime),/wpcom/v2/videopress/meta,/wpcom/v2/videopress/playback-jwt,videopress/v1/site,videopress/v1/features. TanStack Query hooks added alongside./jetpack/v4/videopress/stats/*proxies on the Activity Log pattern (user-signedClient::wpcom_json_api_request_as_user). May spawn a sub-issue if endpoint discovery requires WPCOM-side work.after/beforequery params.rsm_jetpack_ui_modernization_videopressdefault to true. Delete the legacy app atprojects/packages/videopress/src/client/admin/, the legacy webpack entry, and the legacy enqueue branch inclass-admin-ui.php. The wp-build path becomes the only path.Phase 7 discovery outcome (2026-05-11)
The original Phase 3/7/8 plan above is preserved as-written so the rationale for the change is traceable. This section documents what the WPCOM-side investigation surfaced and the resulting design adjustments.
WPCOM-side investigation found two data gaps that change the Overview design relative to the mocks:
Per-video geography is not collected. The
video_statstable has nocountry/regioncolumns, andstats/country-views+stats/location-views/{period}are site-wide (nopost_idfilter). There is no way to build a per-video "Top locations" card from existing data — this is a storage-level gap, not just a missing endpoint, and would require new collection + aggregation on the WPCOM side (a larger Stats Team project).Decision: the "Top locations" card is replaced by "Top videos by watch time", which reuses the same
RankingCardand surfaces engagement rather than reach.Unique-visitor counts aren't recoverable. The
video_statstable doesn't store viewer IDs or session keys, andstats/video-plays?complete_stats=truereturnsviews,impressions, andwatch_timeper day — none of which is a unique-visitor count.Decision: the "Visitors" KPI is renamed to "Impressions". Impressions is real, returned by the same endpoint, and is a coherent reach metric distinct from
views(which require a play).The two-column bottom layout and three-card KPI row are preserved; only the metric in two slots changes.
Deferred follow-ups from this discovery (do not block Phase 7)
page_urlcolumn already present invideo_stats. Would surface a real geography-shaped insight without new collection.Standalone investigations (pending external input)
UpsellCalloutanalogous to Activity Log's, gated server-side in the REST proxy and UI-side via DataViews configuration.VideoStorageMeteris not in the mocks. Map current display conditions in the legacy dashboard, then take to designers to decide on placement (Overview sidebar? Settings? Drop entirely?).Error_Handler::report_errorand JSwp.data.dispatch('jetpack-connection').setConnectionErrors). Likely a real bug inprojects/js-packages/connection/hooks/use-connection-error-notice/. File a separate issue; ensure the new chrome renders the component once fixed.Branch & PRs
Phase 0 landed in #48494 on branch
update/videopress-modernization. Subsequent phases each open their own PR directly to trunk; the modernization filter keeps the in-progress dashboard hidden in production. Each phase's PR ticks the box above when merged.Deferred follow-ups
useAnalyticsTracksin the legacy app, plus new events for the Overview's KPI/chart interactions).Out of scope
@automattic/chartsare the design references; this is a from-scratch rebuild on Gutenberg primitives, not a port.