Track dirty state to skip discard prompt for unchanged drafts

When a draft is loaded and the user hasn't made any changes, closing
the composer should not show the discard prompt since nothing would
be lost.

- Add isDirty field to ComposerState
- Set isDirty: true on all content-modifying actions
- Set isDirty: false on restore_from_draft, clear, and initial state
- Update onPressCancel to only show prompt if no draft or isDirty

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Samuel Newman
2026-01-13 16:34:21 +02:00
parent 10d80dab7b
commit 092528dc05
2 changed files with 29 additions and 9 deletions
+19 -9
View File
@@ -413,21 +413,31 @@ export const ComposePost = ({
const onPressCancel = useCallback(() => {
if (textInput.current?.maybeClosePopup()) {
return
} else if (
thread.posts.some(
post =>
post.shortenedGraphemeLength > 0 ||
post.embed.media ||
post.embed.link,
)
) {
}
const hasContent = thread.posts.some(
post =>
post.shortenedGraphemeLength > 0 || post.embed.media || post.embed.link,
)
// Show discard prompt if there's content AND either:
// - No draft is loaded (new composition)
// - Draft is loaded but has been modified
if (hasContent && (!composerState.draftId || composerState.isDirty)) {
closeAllDialogs()
Keyboard.dismiss()
discardPromptControl.open()
} else {
onClose()
}
}, [thread, closeAllDialogs, discardPromptControl, onClose])
}, [
thread,
composerState.draftId,
composerState.isDirty,
closeAllDialogs,
discardPromptControl,
onClose,
])
useImperativeHandle(cancelRef, () => ({onPressCancel}))
+10
View File
@@ -104,6 +104,8 @@ export type ComposerState = {
mutableNeedsFocusActive: boolean
/** ID of the draft being edited, if any. Used to update existing draft on save. */
draftId?: string
/** Whether the composer has been modified since loading a draft. */
isDirty: boolean
}
export type ComposerAction =
@@ -145,6 +147,7 @@ export function composerReducer(
case 'update_postgate': {
return {
...state,
isDirty: true,
thread: {
...state.thread,
postgate: action.postgate,
@@ -154,6 +157,7 @@ export function composerReducer(
case 'update_threadgate': {
return {
...state,
isDirty: true,
thread: {
...state.thread,
threadgate: action.threadgate,
@@ -174,6 +178,7 @@ export function composerReducer(
}
return {
...state,
isDirty: true,
thread: {
...state.thread,
posts: nextPosts,
@@ -196,6 +201,7 @@ export function composerReducer(
})
return {
...state,
isDirty: true,
thread: {
...state.thread,
posts: nextPosts,
@@ -221,6 +227,7 @@ export function composerReducer(
}
return {
...state,
isDirty: true,
activePostIndex: nextActivePostIndex,
mutableNeedsFocusActive: true,
thread: {
@@ -305,6 +312,7 @@ export function composerReducer(
activePostIndex: 0,
mutableNeedsFocusActive: true,
draftId: draft.id,
isDirty: false,
thread: {
posts,
postgate: draft.postgate || state.thread.postgate,
@@ -317,6 +325,7 @@ export function composerReducer(
activePostIndex: 0,
mutableNeedsFocusActive: true,
draftId: undefined,
isDirty: false,
thread: {
posts: [
{
@@ -699,6 +708,7 @@ export function createComposerState({
return {
activePostIndex: 0,
mutableNeedsFocusActive: false,
isDirty: false,
thread: {
posts: [
{