diff --git a/.changeset/media-inline-dialog-footers.md b/.changeset/media-inline-dialog-footers.md
new file mode 100644
index 0000000000..7fd92282a0
--- /dev/null
+++ b/.changeset/media-inline-dialog-footers.md
@@ -0,0 +1,5 @@
+---
+"sanity-plugin-media": patch
+---
+
+Re-enable React Compiler for the asset details form. The underlying issue is fixed at the root: the form reset that runs when the dialog opens (and when the asset or tag is updated elsewhere) now keeps fields registered (`keepFieldsRef`), so registered string fields keep reporting changes under compiler memoization. The remaining dialog footers (and the confirm dialog header) are also no longer declared as inline components, so they no longer remount whenever form state changes.
diff --git a/plugins/sanity-plugin-media/package.json b/plugins/sanity-plugin-media/package.json
index 95cafe7dfe..c14450da05 100644
--- a/plugins/sanity-plugin-media/package.json
+++ b/plugins/sanity-plugin-media/package.json
@@ -70,6 +70,7 @@
"zod": "^3.25.76"
},
"devDependencies": {
+ "@rolldown/plugin-babel": "catalog:",
"@sanity/tsconfig": "catalog:",
"@sanity/tsdown-config": "catalog:",
"@testing-library/jest-dom": "catalog:",
@@ -79,6 +80,7 @@
"@types/react": "catalog:",
"@types/react-dom": "catalog:",
"@types/react-file-icon": "^1.0.5",
+ "@vitejs/plugin-react": "catalog:",
"babel-plugin-react-compiler": "catalog:",
"jsdom": "catalog:",
"react": "catalog:",
diff --git a/plugins/sanity-plugin-media/src/components/DialogAssetEdit/Details.tsx b/plugins/sanity-plugin-media/src/components/DialogAssetEdit/Details.tsx
index 3c0fb19d9b..4a12503666 100644
--- a/plugins/sanity-plugin-media/src/components/DialogAssetEdit/Details.tsx
+++ b/plugins/sanity-plugin-media/src/components/DialogAssetEdit/Details.tsx
@@ -64,11 +64,6 @@ export default function Details({
creditLine,
locales,
}: DetailsProps) {
- 'use no memo'
- // React Compiler memoizes the register() field JSX using asset-derived deps only.
- // That can leave Save stuck disabled while the DOM still updates (uncontrolled inputs).
- // Tags work because they use Controller; string fields use register and need this opt-out.
-
const hasLocales = locales && locales.length > 0
const [activeLocaleTab, setActiveLocaleTab] = useState(0)
const folderId = currentAsset?.opt?.media?.folder?._ref
diff --git a/plugins/sanity-plugin-media/src/components/DialogAssetEdit/index.tsx b/plugins/sanity-plugin-media/src/components/DialogAssetEdit/index.tsx
index dd9b67e9eb..7ffab25255 100644
--- a/plugins/sanity-plugin-media/src/components/DialogAssetEdit/index.tsx
+++ b/plugins/sanity-plugin-media/src/components/DialogAssetEdit/index.tsx
@@ -331,10 +331,15 @@ const DialogAssetEdit = (props: Props) => {
}
}, [getValues, lastRemovedTagIds, setValue])
- // Reset react-hook-form local state on mount and every time the asset has been updated elsewhere
+ // Reset react-hook-form local state on mount and every time the asset has been updated elsewhere.
+ // `keepFieldsRef` is required with React Compiler: a plain `reset()` empties react-hook-form's
+ // internal field registry and relies on the `register()` calls re-running on the next render to
+ // re-register every field. The compiler memoizes the registered field JSX (keyed on the stable
+ // `register` reference), so without this option the fields stay unregistered after reset and
+ // edits to string fields no longer mark the form dirty (Save stays disabled).
useEffect(() => {
if (assetUpdatedPrev.current !== assetItem?.asset._updatedAt) {
- reset(generateDefaultValues(assetItem?.asset))
+ reset(generateDefaultValues(assetItem?.asset), {keepFieldsRef: true})
}
assetUpdatedPrev.current = assetItem?.asset._updatedAt
}, [assetItem?.asset, generateDefaultValues, reset])
diff --git a/plugins/sanity-plugin-media/src/components/DialogConfirm/index.tsx b/plugins/sanity-plugin-media/src/components/DialogConfirm/index.tsx
index c600a6b7a3..77c6478f86 100644
--- a/plugins/sanity-plugin-media/src/components/DialogConfirm/index.tsx
+++ b/plugins/sanity-plugin-media/src/components/DialogConfirm/index.tsx
@@ -37,7 +37,9 @@ const DialogConfirm = (props: Props) => {
handleClose()
}
- const Footer = () => (
+ // Plain JSX, not inline components: a new component identity per render remounts the
+ // footer/header subtrees whenever captured values change (see DialogAssetEdit for details).
+ const footer = (
@@ -51,7 +53,7 @@ const DialogConfirm = (props: Props) => {
)
- const Header = () => (
+ const header = (
@@ -61,16 +63,7 @@ const DialogConfirm = (props: Props) => {
)
return (
- }
- // oxlint-disable-next-line react/react-compiler
- header={}
- id="confirm"
- onClose={handleClose}
- width={1}
- >
+