fix(ui): stop destroying the comment editor mid-edit on refetch - #261
Conversation
The inline comment editor's initialHtml effect called editor.destroy() in its cleanup, so any change to initialHtml (e.g. a comment-list refetch landing while editing) tore down the live TipTap instance and left a dead ProseMirror mounted, breaking typing and logging errors. useEditor already destroys the editor on unmount, so the manual destroy is gone. Reseeding now runs only when the incoming HTML genuinely differs from what's shown, using emitUpdate:false so it no longer dirties the doc, and the empty state is seeded from initialHtml instead of a mount-time effect. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Strix Security ReviewNo security issues found. Updated for Reviewed by Strix |
📝 WalkthroughWalkthroughCommentEditor.tsx now derives initial emptiness state from a normalized version of initialHtml and rewrites the reseeding effect to compare normalized HTML before resetting editor content, avoiding unnecessary resets and removing the editor.destroy() cleanup call. ChangesComment Editor Reseed Fix
Estimated code review effort: 2 (Simple) | ~10 minutes Sequence Diagram(s)sequenceDiagram
participant IssueDetailPage
participant CommentEditor
participant TipTapEditor
IssueDetailPage->>CommentEditor: initialHtml update (e.g. refetch)
CommentEditor->>CommentEditor: normalize(initialHtml) vs normalize(editor.getHTML())
alt content differs
CommentEditor->>TipTapEditor: setContent(initialHtml, emitUpdate false)
TipTapEditor-->>CommentEditor: updated HTML
CommentEditor->>CommentEditor: recompute isEmpty
else content same
CommentEditor-->>IssueDetailPage: editor untouched, no destroy
end
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
🧹 Nitpick comments (1)
apps/web/src/components/work-item/CommentEditor.tsx (1)
276-281: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winReuse
normalizein the other empty-checks to avoid divergent semantics.
normalizenow maps<p><br></p>to empty, butonUpdate(Line 86) andhandleSubmit(Line 109) still test onlyhtml === '<p></p>' || html === ''. So content of<p><br></p>is treated as empty for the initialisEmptyseed / reseed path yet non-empty byonUpdate(Send gets enabled) and would be submitted byhandleSubmit. Routing all three throughnormalizekeeps emptiness detection consistent.♻️ Suggested consolidation (applies to unchanged Lines 84-87 and 106-113)
onUpdate: ({ editor: ed }) => { - const html = ed.getHTML().trim(); - setIsEmpty(html === '<p></p>' || html === ''); + setIsEmpty(normalize(ed.getHTML()) === ''); },const handleSubmit = () => { if (isSubmitting) return; const html = editor.getHTML().trim(); - if (html === '<p></p>' || html === '') return; + if (normalize(html) === '') return;🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/web/src/components/work-item/CommentEditor.tsx` around lines 276 - 281, `normalize` now treats `<p><br></p>` as empty, but `CommentEditor` still uses separate empty checks in `onUpdate` and `handleSubmit`, causing inconsistent behavior. Update those paths to reuse `normalize` for determining emptiness so the editor’s enabled state, reseed logic, and submit logic all agree on what counts as empty; use the existing `normalize` helper in `CommentEditor` rather than duplicating the HTML comparisons.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@apps/web/src/components/work-item/CommentEditor.tsx`:
- Around line 276-281: `normalize` now treats `<p><br></p>` as empty, but
`CommentEditor` still uses separate empty checks in `onUpdate` and
`handleSubmit`, causing inconsistent behavior. Update those paths to reuse
`normalize` for determining emptiness so the editor’s enabled state, reseed
logic, and submit logic all agree on what counts as empty; use the existing
`normalize` helper in `CommentEditor` rather than duplicating the HTML
comparisons.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: d92c4820-bcd7-4d05-ad92-28026bee7cf8
📒 Files selected for processing (1)
apps/web/src/components/work-item/CommentEditor.tsx
Summary
Closes #138.
The inline comment editor destroyed its own TipTap instance whenever
initialHtmlchanged. The[editor, initialHtml]effect's cleanup callededitor.destroy(), so a comment-list refetch landing while a comment was being edited tore down the live editor and left a dead ProseMirror mounted: typing and formatting stopped working and the console filled with destroyed-instance errors.Changes:
editor.destroy().useEditoralready tears the instance down on unmount, so aninitialHtmlchange no longer kills a live editor.normalizehelper, matchingDescriptionEditor), and uses{ emitUpdate: false }so seeding no longer dirties the doc or firesonUpdate.initialHtmlat mount instead of via a mount-time effect, so the Send button is enabled immediately when editing existing content.Testing
npm run typecheckandnpm run lintpass.contenteditable, Send enabled, and no console errors. Edited the text, saved, and the update persisted; the editor unmounted cleanly (ProseMirror instance count returned to baseline). Deleted the test comment afterward.AI assistance
This change was produced with the help of Claude Code (Claude Opus 4.8). See the
Co-Authored-Bytrailer on the commit.Summary by CodeRabbit