Skip to content

VideoPress Studio: integration branch (flag-gated dashboard expansion)#50168

Draft
vianasw wants to merge 29 commits into
trunkfrom
add/videopress-studio-base
Draft

VideoPress Studio: integration branch (flag-gated dashboard expansion)#50168
vianasw wants to merge 29 commits into
trunkfrom
add/videopress-studio-base

Conversation

@vianasw

@vianasw vianasw commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Fixes # N/A

Proposed changes

Long-lived integration branch for the VideoPress Studio project — a feature-flagged expansion of the modernized VideoPress dashboard (playlists + library management, per-video analytics, a trim & cut video editor, and YouTube metadata import). Work lands on this branch in phases; this PR stays draft and exists primarily so CI runs on every push. Everything is gated behind jetpack_videopress_studio (default off) — with the flag off, no Studio route modules are registered and the dashboard is unchanged.

Currently included (Phase 0 — skeleton):

  • Admin_UI::is_studio_enabled() / jetpack_videopress_studio filter, default false.
  • Server-side route gating: Studio entries are stripped from the generated wp-build route registry on the page init actions when the flag is off, so no Studio JS ships.
  • features.studio in the dashboard initial state + isStudioEnabled() client helper.
  • Flag-gated Playlists tab in the dashboard tab strip; new per-video sub-navigation (Details | Analytics | Editor) via video-nav / video-layout components.
  • Four stub routes: /playlists, /playlists/$id, /video/$id/analytics, /video/$id/editor (placeholder stages, not-found fallback when the flag is off).
  • Jest + PHPUnit coverage for the gating logic.

Related product discussion/links

  • P2: VideoPress Studio: Full-featured Video Library Management (radicalupdates, 2026-04-16)
  • Linear project: VideoPress Studio (RSM team)

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

No.

Testing instructions

  • Build the package: jetpack build packages/videopress --deps.
  • With no configuration (flag defaults off): visit Jetpack → VideoPress and confirm the dashboard is unchanged — Overview / Library / Settings tabs only, no per-video sub-navigation on a video details screen, and deep-linking to admin.php?page=jetpack-videopress&p=%2Fplaylists does not render a Playlists screen.
  • Enable the flag, e.g. in a mu-plugin: add_filter( 'jetpack_videopress_studio', '__return_true' );
  • Confirm a Playlists tab appears between Library and Settings and renders a placeholder at p=/playlists.
  • Open a video from the Library and confirm the Details | Analytics | Editor sub-navigation renders; Analytics and Editor show placeholder screens.
  • Run the suites: jetpack test php packages/videopress and pnpm test from projects/packages/videopress.

vianasw and others added 4 commits July 2, 2026 13:03
…g, initial state)

Adds the jetpack_videopress_studio filter (default off) to Admin_UI, strips
the gated dashboard routes from the generated wp-build route registry when
the flag is off, exposes the gate in the dashboard initial state as
features.studio, types it in global.d.ts, and adds an isStudioEnabled()
client helper plus PHPUnit coverage for the flag and route stripping.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Adds a flag-gated Playlists tab to the dashboard strip (prop-driven via
DashboardLayout), a per-video Details/Analytics/Editor sub-nav (VideoNav)
plus a shared per-video chrome (VideoLayout), and four stub routes —
/playlists, /playlists/$id, /video/$id/analytics, /video/$id/editor —
matching the paths Admin_UI::STUDIO_ROUTE_PATHS strips when the flag is
off. Each stub renders a NotFound fallback when the flag is disabled.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Defer Studio route stripping to the wp-build page init actions
  (priority 5, ahead of the build/routes.php readers at 10) so the
  Studio filter is evaluated at the same request stage Initial_State
  mirrors it to the client, keeping late-registered filters (init,
  admin_init) consistent between server routes and the client flag.
- Make maybe_strip_studio_routes() public for the hook callback and
  drop the reflection helper from Admin_UI_Studio_Test, which also
  removes the PHP < 8.1 setAccessible() hazard.
- Update the DashboardTabs docblock summary to mention the conditional
  Playlists tab.
- Add Jest coverage for the isStudioEnabled() initial-state edge cases.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@vianasw vianasw self-assigned this Jul 2, 2026
@github-actions

github-actions Bot commented Jul 2, 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 add/videopress-studio-base branch.
  • To test on Simple, run the following command on your sandbox:
bin/jetpack-downloader test jetpack add/videopress-studio-base
bin/jetpack-downloader test jetpack-mu-wpcom-plugin add/videopress-studio-base

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 commented Jul 2, 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!

Phan enforces @internal cross-namespace: the test class lived in
Automattic\Jetpack while the methods it exercises are @internal to
Automattic\Jetpack\VideoPress. Align the test namespace with the
package (a convention already used by sibling tests).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@jp-launch-control

jp-launch-control Bot commented Jul 2, 2026

Copy link
Copy Markdown

Code Coverage Summary

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

File Coverage Δ% Δ Uncovered
projects/packages/videopress/src/class-initial-state.php 1/61 (1.64%) -0.08% 3 ❤️‍🩹
projects/packages/videopress/src/class-admin-ui.php 15/250 (6.00%) 6.00% 2 ❤️‍🩹
projects/packages/videopress/src/dashboard/components/dashboard-layout/index.tsx 0/10 (0.00%) 0.00% 2 ❤️‍🩹
projects/packages/videopress/src/dashboard/components/video-details/thumbnail-card.tsx 47/58 (81.03%) -1.66% 2 ❤️‍🩹
projects/packages/videopress/src/class-initializer.php 125/199 (62.81%) 0.06% 1 ❤️‍🩹

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

File Coverage
projects/packages/videopress/src/dashboard/components/playlists/actions.ts 0/5 (0.00%) 💔
projects/packages/videopress/src/dashboard/components/playlists/delete-playlist-modal.tsx 0/16 (0.00%) 💔
projects/packages/videopress/src/dashboard/hooks/use-delete-playlist.ts 0/17 (0.00%) 💔
projects/packages/videopress/src/dashboard/hooks/video-frame-grabber.ts 3/50 (6.00%) 💔
projects/packages/videopress/src/dashboard/components/editor/header-actions.tsx 1/4 (25.00%) ❤️‍🩹

Full summary · PHP report · JS report

Coverage check overridden by I don't care about code coverage for this PR Use this label to ignore the check for insufficient code coveage. .

vianasw and others added 17 commits July 2, 2026 14:27
Register the vps_playlist attachment taxonomy and its term meta when the
jetpack_videopress_studio filter is on (default off):

- Taxonomy is REST-only (public/show_ui false, show_in_rest true) with
  rest_base 'videopress-playlists', all capabilities mapped to
  upload_files, and _update_generic_term_count so counts work for
  unattached inherit-status attachments.
- Term meta: vps_playlist_artwork_id (integer), vps_playlist_type
  (enum collection|series|course|season, invalid values coerce to
  collection), vps_playlist_order (array of positive ints, deduped).
  Membership truth stays in term relationships; the order meta is
  presentation-only and written atomically per reorder.
- delete_attachment prunes the deleted ID from every playlist's order
  meta before the term relationships vanish.
- No Jetpack Sync handling for the term meta (known limitation).

Tests live in a new sqlite-backed suite (tests/php-sqlite) because the
default WorDBless dbless engine has no term-table backing; the suite
bootstrap heals the WorDBless sqlite install (missing db_version
disables term meta entirely, and default roles are absent). The suite
verifies the core REST media endpoint accepts
{ "videopress-playlists": [term_id] } on POST /wp/v2/media/{id} and
filters GET /wp/v2/media?videopress-playlists={term_id}.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…reorder

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…r entry

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Gate playlist REST reads on upload_files via a terms-controller
  subclass; core allows anonymous view-context reads of any
  show_in_rest taxonomy.
- Invalidate the library cache (and swallow the presentation-only
  order-prune failure) in useRemoveFromPlaylist so stale playlistIds
  can't resurrect a removed membership via the add modal.
- Re-read each attachment's playlist terms right before the
  replace-set write in useSetPlaylists to narrow the cross-client
  last-write-wins race.
- Wire Playlists::init() unconditionally; register() re-checks the
  Studio flag at init time so late-added filters are honored.
- Replace the isMutating settle guard in useReorderPlaylist with a
  module counter; overlapping reorders settling together could both
  skip the invalidation.
- Clear vps_playlist_artwork_id when the artwork attachment is
  deleted; document the MEDIA_TRASH count limitation.
- Report per-playlist results from useDeletePlaylist and surface
  partial failures in the delete modal.
- Use _n() for the add-modal partial notice and the bulk delete
  confirmation; artwork-specific media-frame labels; stable empty
  playlists identity; declare @wordpress/url in routes/playlists.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Adds GET /jetpack/v4/videopress/stats/video/{post_id} as a blog-signed
proxy to the WPCOM v1.1 sites/{blog_id}/stats/video/{post_id} endpoint,
returning the upstream body as a tolerant passthrough for the upcoming
Studio per-video analytics screen. Admin-gated like the sibling
video-plays route, with validated period/num/end_date params.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Move KpiCard, ViewsTrendsCard, and DateRangeSelector from
components/overview/ to a new components/stats/ directory, and move the
KPI / skeleton / chart-frame / chart-tooltip SCSS they consume from
routes/overview/style.scss into a component-level stats/style.scss
imported by the components themselves, so the upcoming per-video
analytics route can reuse them without pulling in the Overview page
stylesheet. Compiled selectors and rendered output are unchanged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Trim/cut reducer with clamped invariants (integer ms, sorted merged cuts,
1s minimum output), gesture-coalescing undo history, canonical operations
serialization, timeline px/ms + snapping + ruler math, and the playback
skip engine — all dependency-free TypeScript with exhaustive jest coverage.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…oint

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…r entry

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Zoomable ruler + filmstrip-placeholder strip with playhead scrubbing,
pointer-captured trim handles and cut segments dispatching
TRANSIENT/COMMIT into the session history, a New cut toolbar with an
editable timecode box and log-scale zoom control, and document-level
keyboard shortcuts. No new dependencies: absolutely-positioned divs and
native Pointer Events throughout.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Surface stats query errors instead of silent zeroed KPIs (error card
replaces the KPI/chart region on both the Overview and per-video
screens, and the channel-comparison table gets an explicit failure
state), validate YYYY-MM-DD date args in the stats proxies (core skips
format: date), deduplicate the two WPCOM proxy callbacks into shared
helpers, drop the unconsumed dailyPlays surface from useVideoPages,
correct the REST controller docblock about which endpoint feeds the
plays chart, and document why the pages card ignores the date range.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…HP mock

The contract for the wpcom ffmpeg pipeline: GET/POST/DELETE
wpcom/v2/videopress/{guid}/edits plus a storyboard stub, served locally by
a single deletable mock class (lazy job promotion, deterministic failure
path, state in one site option) gated on the Studio flag. Frontend gets
mirrored transport types and react-query hooks: useVideoEdits (2s polling
while processing), useSaveVideoEdits (typed 409 conflict error), and
useRestoreOriginal.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@vianasw vianasw added the I don't care about code coverage for this PR Use this label to ignore the check for insufficient code coveage. label Jul 2, 2026
vianasw and others added 7 commits July 2, 2026 18:02
- Playlist detail artwork no longer flashes the 'No artwork' placeholder
  while the members fetch is in flight: videosLoading is threaded into
  PlaylistDetailArtwork so usePlaylistArtwork falls back to its own
  order[0] lookup (usually cached from the list) during the load.
- The details screen's 'Add to playlist' button now gates on the same
  idle predicate as the library bulk action, so uploading/processing
  videos deep-linked into the details route can't be added to playlists.
- The thumbnail card's action-button row wraps on narrow viewports
  instead of overflowing the card when both buttons render.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…lows

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…xtraction fallback

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ntry

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…g, undo history, and navigation guards

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant