fix(npcs): stop TipTap crash black screen in secondary windows

Guard TipTap getHTML under StrictMode, wrap secondary window roots in WindowErrorBoundary, and add stability regression tests.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Ivan Fontosh
2026-07-30 09:10:47 +08:00
parent e687303c57
commit 04c75cd725
15 changed files with 305 additions and 64 deletions
+45 -10
View File
@@ -73,7 +73,7 @@ export function SceneDescriptionModal({ initialHtml, onClose, onSave }: SceneDes
const editor = useEditor({
extensions,
content: initialHtml || '',
immediatelyRender: true,
immediatelyRender: false,
shouldRerenderOnTransaction: true,
editorProps: {
attributes: {
@@ -94,21 +94,56 @@ export function SceneDescriptionModal({ initialHtml, onClose, onSave }: SceneDes
const toolbarState = useEditorState({
editor,
selector: ({ editor: ed }) => ({
bold: ed.isActive('bold'),
italic: ed.isActive('italic'),
underline: ed.isActive('underline'),
bulletList: ed.isActive('bulletList'),
orderedList: ed.isActive('orderedList'),
h2: ed.isActive('heading', { level: 2 }),
h3: ed.isActive('heading', { level: 3 }),
blockquote: ed.isActive('blockquote'),
bold: Boolean(ed && !ed.isDestroyed && ed.isActive('bold')),
italic: Boolean(ed && !ed.isDestroyed && ed.isActive('italic')),
underline: Boolean(ed && !ed.isDestroyed && ed.isActive('underline')),
bulletList: Boolean(ed && !ed.isDestroyed && ed.isActive('bulletList')),
orderedList: Boolean(ed && !ed.isDestroyed && ed.isActive('orderedList')),
h2: Boolean(ed && !ed.isDestroyed && ed.isActive('heading', { level: 2 })),
h3: Boolean(ed && !ed.isDestroyed && ed.isActive('heading', { level: 3 })),
blockquote: Boolean(ed && !ed.isDestroyed && ed.isActive('blockquote')),
}),
});
const handleSave = () => {
onSave(normalizeSceneDescriptionHtml(editor.getHTML()));
if (!editor || editor.isDestroyed) return;
let raw = '';
try {
raw = editor.getHTML();
} catch {
return;
}
onSave(normalizeSceneDescriptionHtml(raw));
};
if (!editor || editor.isDestroyed) {
return createPortal(
<>
<button
type="button"
aria-label={t('common.close')}
onClick={onClose}
className={styles.modalBackdrop}
/>
<div role="dialog" aria-modal="true" className={[styles.modalDialog, modalStyles.dialog].join(' ')}>
<div className={styles.modalHeader}>
<div className={styles.modalTitle}>{t('scene.descriptionModalTitle')}</div>
<button
type="button"
aria-label={t('common.close')}
onClick={onClose}
className={styles.modalClose}
>
×
</button>
</div>
<div className={modalStyles.editorShell} />
</div>
</>,
document.body,
);
}
return createPortal(
<>
<button