mirror of
https://github.com/simstudioai/sim.git
synced 2026-09-24 15:45:35 +08:00
fix(mothership): catch draft restore errors instead of crashing /home (#4433)
* fix(mothership): catch draft restore errors instead of crashing /home Wrap the mount-time draft restore in try/catch with clearDraft on throw, and coerce text to a string in the useState initializer. A corrupt entry in mothership-drafts:v1 localStorage previously took down the entire workspace via the error boundary. * fix(mothership): defer state writes and log restore failures Build the restored state in locals first and only apply on success so a partial throw can't leave stale contexts in the UI with the draft already cleared. Switch the empty catch to logger.error so corrupt-draft incidents surface in production logs.
This commit is contained in:
@@ -11,6 +11,7 @@ import {
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react'
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { Paperclip } from 'lucide-react'
|
||||
import { useParams } from 'next/navigation'
|
||||
import { Button, Tooltip } from '@/components/emcn'
|
||||
@@ -58,6 +59,8 @@ import type { ChatContext } from '@/stores/panel'
|
||||
|
||||
export type { FileAttachmentForApi } from '@/app/workspace/[workspaceId]/home/types'
|
||||
|
||||
const logger = createLogger('UserInput')
|
||||
|
||||
function getCaretAnchor(
|
||||
textarea: HTMLTextAreaElement,
|
||||
caretPos: number
|
||||
@@ -148,7 +151,8 @@ export const UserInput = forwardRef<UserInputHandle, UserInputProps>(function Us
|
||||
const [value, setValue] = useState(() => {
|
||||
if (defaultValue) return defaultValue
|
||||
if (!draftScopeKey) return ''
|
||||
return useMothershipDraftsStore.getState().drafts[draftScopeKey]?.text ?? ''
|
||||
const text = useMothershipDraftsStore.getState().drafts[draftScopeKey]?.text
|
||||
return typeof text === 'string' ? text : ''
|
||||
})
|
||||
const overlayRef = useRef<HTMLDivElement>(null)
|
||||
const plusMenuRef = useRef<PlusMenuHandle>(null)
|
||||
@@ -189,14 +193,17 @@ export const UserInput = forwardRef<UserInputHandle, UserInputProps>(function Us
|
||||
useEffect(() => {
|
||||
if (hasRestoredDraftRef.current || !draftScopeKey) return
|
||||
hasRestoredDraftRef.current = true
|
||||
const draft = useMothershipDraftsStore.getState().drafts[draftScopeKey]
|
||||
if (!draft) return
|
||||
if (draft.contexts?.length) {
|
||||
contextManagement.setSelectedContexts(draft.contexts)
|
||||
}
|
||||
if (draft.fileAttachments?.length) {
|
||||
files.restoreAttachedFiles(
|
||||
draft.fileAttachments.map((a) => ({
|
||||
let restoredContexts: ChatContext[] | null = null
|
||||
let restoredFiles: AttachedFile[] | null = null
|
||||
let caretText: string | null = null
|
||||
try {
|
||||
const draft = useMothershipDraftsStore.getState().drafts[draftScopeKey]
|
||||
if (!draft) return
|
||||
if (draft.contexts?.length) {
|
||||
restoredContexts = draft.contexts
|
||||
}
|
||||
if (draft.fileAttachments?.length) {
|
||||
restoredFiles = draft.fileAttachments.map((a) => ({
|
||||
id: a.id,
|
||||
name: a.filename,
|
||||
size: a.size,
|
||||
@@ -205,13 +212,22 @@ export const UserInput = forwardRef<UserInputHandle, UserInputProps>(function Us
|
||||
key: a.key,
|
||||
uploading: false,
|
||||
}))
|
||||
)
|
||||
}
|
||||
if (typeof draft.text === 'string' && draft.text.length > 0) {
|
||||
caretText = draft.text
|
||||
}
|
||||
} catch (err) {
|
||||
logger.error('Failed to read draft, clearing', { err })
|
||||
useMothershipDraftsStore.getState().clearDraft(draftScopeKey)
|
||||
return
|
||||
}
|
||||
if (draft.text) {
|
||||
if (restoredContexts) contextManagement.setSelectedContexts(restoredContexts)
|
||||
if (restoredFiles) files.restoreAttachedFiles(restoredFiles)
|
||||
if (caretText !== null) {
|
||||
const textarea = textareaRef.current
|
||||
if (textarea) {
|
||||
textarea.focus()
|
||||
textarea.setSelectionRange(draft.text.length, draft.text.length)
|
||||
textarea.setSelectionRange(caretText.length, caretText.length)
|
||||
}
|
||||
}
|
||||
}, []) // eslint-disable-line react-hooks/exhaustive-deps -- intentional mount-only restore
|
||||
|
||||
Reference in New Issue
Block a user