diff --git a/packages/sanity/src/core/i18n/bundles/studio.ts b/packages/sanity/src/core/i18n/bundles/studio.ts
index 9d2592329de..53db19bbd01 100644
--- a/packages/sanity/src/core/i18n/bundles/studio.ts
+++ b/packages/sanity/src/core/i18n/bundles/studio.ts
@@ -1184,20 +1184,14 @@ export const studioLocaleStrings = defineLocalesResources('studio', {
'release.deleted-tooltip': 'This release has been deleted',
/** Title for creating releases dialog */
'release.dialog.create.title': 'Create release',
- /** Title for editing releases dialog */
- 'release.dialog.edit.title': 'Edit release',
- /** Label for the description form field when creating releases */
- 'release.form.description': 'Description',
- /** Placeholder for the icon and colour picker */
- 'release.form.search-icon': 'Search icons',
- /** Tooltip label for the icon display */
- 'release.form.search-icon-tooltip': 'Select release icon',
- /** Label for the title form field when creating releases */
- 'release.form.title': 'Title',
+ /** The placeholder text when the release doesn't have a description */
+ 'release.form.placeholer-describe-release': 'Describe the release…',
/** Tooltip for button to hide release visibility */
'release.layer.hide': 'Hide release',
/** Tooltip for releases navigation in navbar */
'release.navbar.tooltip': 'Releases',
+ /** The placeholder text when the release doesn't have a title */
+ 'release.placeholder-untitled-release': 'Untitled release',
/** Label for the release type 'as soon as possible' */
'release.type.asap': 'ASAP',
/** Label for the release type 'at time', meaning it's a release with a scheduled date */
diff --git a/packages/sanity/src/core/releases/components/dialog/ReleaseDetailsDialog.tsx b/packages/sanity/src/core/releases/components/dialog/ReleaseDetailsDialog.tsx
index 6a4a728c84b..f72c029bbc0 100644
--- a/packages/sanity/src/core/releases/components/dialog/ReleaseDetailsDialog.tsx
+++ b/packages/sanity/src/core/releases/components/dialog/ReleaseDetailsDialog.tsx
@@ -96,8 +96,7 @@ export function ReleaseDetailsDialog(props: ReleaseDetailsDialogProps): JSX.Elem
setValue(changedValue)
}, [])
- const dialogTitle =
- formAction === 'edit' ? t('release.dialog.edit.title') : t('release.dialog.create.title')
+ const dialogTitle = t('release.dialog.create.title')
const isReleaseScheduled =
release && (release.state === 'scheduled' || release.state === 'scheduling')
@@ -115,7 +114,7 @@ export function ReleaseDetailsDialog(props: ReleaseDetailsDialogProps): JSX.Elem
diff --git a/packages/sanity/src/core/releases/tool/detail/ReleaseDashboardDetails.tsx b/packages/sanity/src/core/releases/tool/detail/ReleaseDashboardDetails.tsx
index 201cef7c38e..d6de0bdb2b0 100644
--- a/packages/sanity/src/core/releases/tool/detail/ReleaseDashboardDetails.tsx
+++ b/packages/sanity/src/core/releases/tool/detail/ReleaseDashboardDetails.tsx
@@ -89,10 +89,7 @@ export function ReleaseDashboardDetails({release}: {release: ReleaseDocument}) {
-
+
diff --git a/packages/sanity/src/core/releases/tool/detail/ReleaseDetailsEditor.test.tsx b/packages/sanity/src/core/releases/tool/detail/ReleaseDetailsEditor.test.tsx
new file mode 100644
index 00000000000..044a4ec5689
--- /dev/null
+++ b/packages/sanity/src/core/releases/tool/detail/ReleaseDetailsEditor.test.tsx
@@ -0,0 +1,73 @@
+import {fireEvent, render, screen, waitFor} from '@testing-library/react'
+import {beforeEach, describe, expect, it, vi} from 'vitest'
+
+import {createTestProvider} from '../../../../../test/testUtils/TestProvider'
+import {type ReleaseDocument, useReleaseOperations} from '../../../store'
+import {ReleaseDetailsEditor} from './ReleaseDetailsEditor'
+
+// Mock the dependencies
+vi.mock('../../../store/release/useReleaseOperations', () => ({
+ useReleaseOperations: vi.fn().mockReturnValue({
+ updateRelease: vi.fn(),
+ }),
+}))
+
+describe('ReleaseDetailsEditor', () => {
+ beforeEach(async () => {
+ const initialRelease = {
+ _id: 'release1',
+ metadata: {
+ title: 'Initial Title',
+ description: '',
+ releaseType: 'asap',
+ intendedPublishAt: undefined,
+ },
+ } as ReleaseDocument
+ const wrapper = await createTestProvider()
+ render(, {wrapper})
+ })
+
+ it('should call updateRelease after title change', () => {
+ const release = {
+ _id: 'release1',
+ metadata: {
+ title: 'New Title',
+ description: '',
+ releaseType: 'asap',
+ intendedPublishAt: undefined,
+ },
+ } as ReleaseDocument
+
+ const input = screen.getByTestId('release-form-title')
+ fireEvent.change(input, {target: {value: release.metadata.title}})
+
+ waitFor(
+ () => {
+ expect(useReleaseOperations().updateRelease).toHaveBeenCalledWith(release)
+ },
+ {timeout: 250},
+ )
+ })
+
+ it('should call updateRelease after description change', () => {
+ const release = {
+ _id: 'release1',
+ metadata: {
+ title: 'Initial Title',
+ description: 'woo hoo',
+ releaseType: 'asap',
+ intendedPublishAt: undefined,
+ },
+ } as ReleaseDocument
+
+ const input = screen.getByTestId('release-form-description')
+ fireEvent.change(input, {target: {value: release.metadata.description}})
+
+ waitFor(
+ () => {
+ expect(useReleaseOperations().updateRelease).toHaveBeenCalledWith(release)
+ },
+ {timeout: 250},
+ )
+ })
+})
diff --git a/packages/sanity/src/core/releases/tool/detail/ReleaseDetailsEditor.tsx b/packages/sanity/src/core/releases/tool/detail/ReleaseDetailsEditor.tsx
index f2b25b126de..a88f39e99d5 100644
--- a/packages/sanity/src/core/releases/tool/detail/ReleaseDetailsEditor.tsx
+++ b/packages/sanity/src/core/releases/tool/detail/ReleaseDetailsEditor.tsx
@@ -1,27 +1,29 @@
-import {Box, Heading, Text} from '@sanity/ui'
+import {useCallback, useState} from 'react'
-import {type ReleaseDocument} from '../../../store'
+import {
+ type EditableReleaseDocument,
+ type ReleaseDocument,
+ useReleaseOperations,
+} from '../../../store'
+import {ReleaseInputsForm} from '../../components/dialog/ReleaseInputsForm'
-// TODO: This is not a working example, it's just a placeholder. A proper editor should be implemented.
-export function ReleaseDetailsEditor({
- description,
- title,
-}: {
- description: ReleaseDocument['metadata']['description']
- title: ReleaseDocument['metadata']['title']
-}) {
- return (
- <>
-
-
- {title}
-
-
-
-
- {description || 'Describe the release...'}
-
-
- >
+export function ReleaseDetailsEditor({release}: {release: ReleaseDocument}): JSX.Element {
+ const {updateRelease} = useReleaseOperations()
+ const [timer, setTimer] = useState(undefined)
+
+ const handleOnChange = useCallback(
+ (changedValue: EditableReleaseDocument) => {
+ clearTimeout(timer)
+
+ /** @todo I wasn't able to get this working with the debouncer that we use in other parts */
+ const newTimer = setTimeout(() => {
+ updateRelease(changedValue)
+ }, 200)
+
+ setTimer(newTimer)
+ },
+ [timer, updateRelease],
)
+
+ return
}
diff --git a/packages/sanity/src/structure/panes/document/documentPanel/header/perspective/DocumentPerspectiveList.tsx b/packages/sanity/src/structure/panes/document/documentPanel/header/perspective/DocumentPerspectiveList.tsx
index 6ac04176f3a..5d3e86a9eca 100644
--- a/packages/sanity/src/structure/panes/document/documentPanel/header/perspective/DocumentPerspectiveList.tsx
+++ b/packages/sanity/src/structure/panes/document/documentPanel/header/perspective/DocumentPerspectiveList.tsx
@@ -175,7 +175,7 @@ export const DocumentPerspectiveList = memo(function DocumentPerspectiveList() {
tooltipContent={}
selected={release.name === getVersionFromId(displayed?._id || '')}
onClick={handleBundleChange(release.name)}
- text={release.metadata.title}
+ text={release.metadata.title || t('release.placeholder-untitled-release')}
tone={getReleaseTone(release)}
contextValues={{
documentId: displayed?._id || '',
diff --git a/packages/sanity/src/structure/panes/document/documentPanel/header/perspective/contextMenu/VersionContextMenu.tsx b/packages/sanity/src/structure/panes/document/documentPanel/header/perspective/contextMenu/VersionContextMenu.tsx
index 73906e0ea7c..b08ca6f5a68 100644
--- a/packages/sanity/src/structure/panes/document/documentPanel/header/perspective/contextMenu/VersionContextMenu.tsx
+++ b/packages/sanity/src/structure/panes/document/documentPanel/header/perspective/contextMenu/VersionContextMenu.tsx
@@ -72,7 +72,6 @@ export const VersionContextMenu = memo(function VersionContextMenu(props: {
as="a"
key={option.value._id}
onClick={() => onCreateVersion(option.value._id)}
- text={option.value.metadata?.title}
renderMenuItem={() => }
disabled={disabled}
/>
diff --git a/packages/sanity/src/structure/panes/document/documentPanel/header/perspective/contextMenu/VersionContextMenuItem.tsx b/packages/sanity/src/structure/panes/document/documentPanel/header/perspective/contextMenu/VersionContextMenuItem.tsx
index 4bb998a07f9..7c6d7a7fb73 100644
--- a/packages/sanity/src/structure/panes/document/documentPanel/header/perspective/contextMenu/VersionContextMenuItem.tsx
+++ b/packages/sanity/src/structure/panes/document/documentPanel/header/perspective/contextMenu/VersionContextMenuItem.tsx
@@ -23,7 +23,7 @@ export const VersionContextMenuItem = memo(function VersionContextMenuItem(props
- {release.metadata.title}
+ {release.metadata?.title ?? t('release.placeholder-untitled-release')}
{release.metadata.releaseType === 'asap' && <>{t('release.type.asap')}>}