fix(composer): recover silently from a composer render error; capture details
CI / Build & Quality Checks (push) Successful in 1m49s
CI / Docker image build & smoke test (push) Skipped
CI / Secret scan (gitleaks) (push) Successful in 9s
CI / Trigger Desktop Build (push) Successful in 9s
CI / Playwright smoke (e2e) (push) Canceled after 3m3s

Reported on chat.lotusguild.org: picking a name from the @-mention list
showed "The message composer hit a snag." — clicking OK continued with the
draft intact. The composer's known failure mode is a transient render error
right after an autocomplete insert (slate-react's DOM selection sync racing
the model; 477df4ae fixed one such path). It did not reproduce locally in 60
attempts (click / Enter / Tab, fast and slow, display names with spaces and
emoji, Chromium and WebKit), so this makes it harmless and diagnosable:

- ComposerErrorBoundary: the first failure clears the selection and remounts
  the composer immediately, with no notice (the draft is intact). A second
  failure within 5 s shows the notice, now saying the draft is safe, with
  "Reload composer" and "Copy details" (time, error, stack, component stack,
  browser). Every failure is logged to the console as "[composer] render
  error".
- Used for the room composer and, newly, the thread panel composer (which had
  no boundary, so the same error took the whole panel down).

Verified with a temporary injected crash (not committed): one crash recovers
with the draft kept and no notice; two within 5 s show the notice, Copy
details copies the report, Reload composer restores the full draft.
Chromium e2e 19 passed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PPmy3tPq869XDW4njjVaKA
This commit is contained in:
Lotus CI
2026-09-25 18:08:33 -04:00
co-authored by Claude Opus 5.5
parent afd14719ef
commit 4c9af57a97
3 changed files with 140 additions and 46 deletions
+12 -9
View File
@@ -23,6 +23,7 @@ import { useKeyDown } from '../../../hooks/useKeyDown';
import { useSetting } from '../../../state/hooks/settings';
import { settingsAtom } from '../../../state/settings';
import { RoomInput } from '../RoomInput';
import { ComposerErrorBoundary } from '../ComposerErrorBoundary';
import {
getThreadNotificationModeIcon,
ThreadNotificationModeSwitcher,
@@ -204,15 +205,17 @@ export function ThreadPanel({ room, threadId, requestClose }: ThreadPanelProps)
<ThreadTimeline room={room} thread={thread} editor={editor} />
</Box>
<Box className={css.ThreadPanelInput} shrink="No" direction="Column">
<RoomInput
room={room}
roomId={room.roomId}
threadRootId={threadId}
editor={editor}
editableName="ThreadInput"
fileDropContainerRef={fileDropContainerRef}
compactLayout
/>
<ComposerErrorBoundary editor={editor}>
<RoomInput
room={room}
roomId={room.roomId}
threadRootId={threadId}
editor={editor}
editableName="ThreadInput"
fileDropContainerRef={fileDropContainerRef}
compactLayout
/>
</ComposerErrorBoundary>
</Box>
</>
)}