From 77db725ecb113eb4b29970cdeaac5263b7aa87e1 Mon Sep 17 00:00:00 2001 From: Christian Gastrell Date: Tue, 2 Jun 2026 10:48:25 -0300 Subject: [PATCH 1/2] VideoPress: fix legacy admin crash on upload/delete from base-ui ref-merge MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The legacy VideoPress admin dashboard crashed with an uncaught TypeError from base-ui@1.4.1's useMergedRefs/useRenderElement path whenever a video was uploaded or deleted (regression from #48909, which migrated UpgradeTrigger to a @wordpress/ui Notice compound tree). UpgradeTrigger conditionally mounted based on hasUsedVideo (which flips on upload/delete). That changed the Notice.Root subtree shape across renders; base-ui@1.4.1's useRenderElement conditionally swaps between two different ref-merge hooks (separate useRef slots), so the shifting shape misaligned the stored fork-ref and didChange read .refs off null. Fix: always render , varying only its text — mirroring the modern dashboard's free-tier-notice.tsx, which uses the same Notice and never crashes because its Description is static. A non-empty fallback nudge covers the pre-first-upload case so we never render an empty styled row. Keeps the JETPACK-1543 @wordpress/ui Notice migration; does not revert #48909. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../fix-vidp-245-upgradetrigger-base-ui-crash | 4 ++++ .../src/client/admin/components/admin-page/index.tsx | 12 ++++++++++-- .../fix-vidp-245-upgradetrigger-base-ui-crash | 4 ++++ .../fix-vidp-245-upgradetrigger-base-ui-crash | 4 ++++ 4 files changed, 22 insertions(+), 2 deletions(-) create mode 100644 projects/packages/videopress/changelog/fix-vidp-245-upgradetrigger-base-ui-crash create mode 100644 projects/plugins/jetpack/changelog/fix-vidp-245-upgradetrigger-base-ui-crash create mode 100644 projects/plugins/videopress/changelog/fix-vidp-245-upgradetrigger-base-ui-crash diff --git a/projects/packages/videopress/changelog/fix-vidp-245-upgradetrigger-base-ui-crash b/projects/packages/videopress/changelog/fix-vidp-245-upgradetrigger-base-ui-crash new file mode 100644 index 000000000000..fb278af93a12 --- /dev/null +++ b/projects/packages/videopress/changelog/fix-vidp-245-upgradetrigger-base-ui-crash @@ -0,0 +1,4 @@ +Significance: patch +Type: fixed + +Fix admin page crash on video upload/delete when the free-plan upgrade nudge is shown diff --git a/projects/packages/videopress/src/client/admin/components/admin-page/index.tsx b/projects/packages/videopress/src/client/admin/components/admin-page/index.tsx index ea63b60545ec..78f956a5c4c1 100644 --- a/projects/packages/videopress/src/client/admin/components/admin-page/index.tsx +++ b/projects/packages/videopress/src/client/admin/components/admin-page/index.tsx @@ -236,9 +236,17 @@ const UpgradeTrigger = ( { hasUsedVideo = false }: { hasUsedVideo: boolean } ) = run ); + // VIDP-245: keep the `Notice.Root` children shape invariant across the + // `hasUsedVideo` flip. base-ui@1.4.1's `useRenderElement` swaps between two + // different ref-merge hooks depending on subtree shape, so conditionally + // mounting `` (as this did before) misaligns its stored + // fork-ref and crashes on the next upload/delete. Always render the + // Description — mirroring the modern dashboard's `free-tier-notice.tsx` — + // varying only its text, with a non-empty fallback nudge before the first + // upload so we never render an empty styled row. const description = hasUsedVideo ? __( 'You have used your free video upload', 'jetpack-videopress-pkg' ) - : ''; + : __( 'The free plan includes one video upload.', 'jetpack-videopress-pkg' ); const cta = __( 'Upgrade now to unlock unlimited videos, 1TB of storage, and more!', @@ -247,7 +255,7 @@ const UpgradeTrigger = ( { hasUsedVideo = false }: { hasUsedVideo: boolean } ) = return ( - { description && { description } } + { description } { cta } diff --git a/projects/plugins/jetpack/changelog/fix-vidp-245-upgradetrigger-base-ui-crash b/projects/plugins/jetpack/changelog/fix-vidp-245-upgradetrigger-base-ui-crash new file mode 100644 index 000000000000..8e717ff646f3 --- /dev/null +++ b/projects/plugins/jetpack/changelog/fix-vidp-245-upgradetrigger-base-ui-crash @@ -0,0 +1,4 @@ +Significance: patch +Type: bugfix + +VideoPress: fix admin page crash on video upload/delete when the free-plan upgrade nudge is shown diff --git a/projects/plugins/videopress/changelog/fix-vidp-245-upgradetrigger-base-ui-crash b/projects/plugins/videopress/changelog/fix-vidp-245-upgradetrigger-base-ui-crash new file mode 100644 index 000000000000..fb278af93a12 --- /dev/null +++ b/projects/plugins/videopress/changelog/fix-vidp-245-upgradetrigger-base-ui-crash @@ -0,0 +1,4 @@ +Significance: patch +Type: fixed + +Fix admin page crash on video upload/delete when the free-plan upgrade nudge is shown From c754c0c24c475f65b44f39aab5c910b51fbbaf71 Mon Sep 17 00:00:00 2001 From: Christian Gastrell Date: Tue, 2 Jun 2026 11:03:44 -0300 Subject: [PATCH 2/2] VideoPress: hoist UpgradeTrigger i18n strings to avoid terser ternary-fold The previous fix put both translated strings as the two arms of an inline ternary. Terser folds `cond ? __('a',d) : __('b',d)` into a single `__(cond?'a':'b', d)`, whose msgid is no longer a string literal, so the i18n-check-webpack-plugin fails the production build (makepot can't extract it). Hoist each string to its own module-scope constant so the calls stay separate literal statements. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../admin/components/admin-page/index.tsx | 20 ++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/projects/packages/videopress/src/client/admin/components/admin-page/index.tsx b/projects/packages/videopress/src/client/admin/components/admin-page/index.tsx index 78f956a5c4c1..ebe083924eb0 100644 --- a/projects/packages/videopress/src/client/admin/components/admin-page/index.tsx +++ b/projects/packages/videopress/src/client/admin/components/admin-page/index.tsx @@ -211,6 +211,19 @@ const Admin = () => { export default Admin; +// VIDP-245: keep these `__()` calls at module scope as separate statements. +// If they live as the two arms of an inline ternary, terser folds them into a +// single `__( cond ? 'a' : 'b', domain )`, which breaks string-literal POT +// extraction (the i18n-check-webpack-plugin fails the production build). +const UPGRADE_TRIGGER_USED_VIDEO_TEXT = __( + 'You have used your free video upload', + 'jetpack-videopress-pkg' +); +const UPGRADE_TRIGGER_FREE_PLAN_TEXT = __( + 'The free plan includes one video upload.', + 'jetpack-videopress-pkg' +); + const UpgradeTrigger = ( { hasUsedVideo = false }: { hasUsedVideo: boolean } ) => { const { adminUri, siteSuffix } = window.jetpackVideoPressInitialState; @@ -243,10 +256,11 @@ const UpgradeTrigger = ( { hasUsedVideo = false }: { hasUsedVideo: boolean } ) = // fork-ref and crashes on the next upload/delete. Always render the // Description — mirroring the modern dashboard's `free-tier-notice.tsx` — // varying only its text, with a non-empty fallback nudge before the first - // upload so we never render an empty styled row. + // upload so we never render an empty styled row. The two strings are hoisted + // to module scope (above) to avoid the terser i18n ternary-fold. const description = hasUsedVideo - ? __( 'You have used your free video upload', 'jetpack-videopress-pkg' ) - : __( 'The free plan includes one video upload.', 'jetpack-videopress-pkg' ); + ? UPGRADE_TRIGGER_USED_VIDEO_TEXT + : UPGRADE_TRIGGER_FREE_PLAN_TEXT; const cta = __( 'Upgrade now to unlock unlimited videos, 1TB of storage, and more!',