fix(threads): polls created from a thread composer land in the thread

PollCreator used the legacy 3-arg sendEvent (threadId null). Thread the
composer's threadRootId through and send like the sticker path.

Also (#41, same file): composer drafts are persisted as { userId, nodes }
and the restore path drops any draft with a different or missing userId.

Fixes #35

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PPmy3tPq869XDW4njjVaKA
This commit is contained in:
2026-09-12 14:48:34 -04:00
co-authored by Claude Opus 5
parent 4dd0e6637d
commit e447fdc0f3
2 changed files with 44 additions and 13 deletions
+6 -2
View File
@@ -25,10 +25,12 @@ import { useModalStyle } from '../../hooks/useModalStyle';
interface PollCreatorProps {
roomId: string;
room: Room;
/** Set when the composer is inside a thread so the poll lands in that thread. */
threadRootId?: string;
onClose: () => void;
}
export function PollCreator({ roomId, onClose }: PollCreatorProps) {
export function PollCreator({ roomId, threadRootId, onClose }: PollCreatorProps) {
const mx = useMatrixClient();
const modalStyle = useModalStyle(440);
const [question, setQuestion] = useState('');
@@ -85,7 +87,9 @@ export function PollCreator({ roomId, onClose }: PollCreatorProps) {
const fallbackBody = [trimmedQuestion, ...filledOptions.map((o, i) => `${i + 1}. ${o}`)].join(
'\n',
);
await mx.sendEvent(roomId, 'm.poll.start' as any, {
// Pass the thread id explicitly (like the sticker path in RoomInput); the
// legacy 3-arg form always resolves to the main timeline.
await mx.sendEvent(roomId, threadRootId ?? null, 'm.poll.start' as any, {
'm.poll': {
question: { 'm.text': trimmedQuestion },
answers: filledOptions.map((o, i) => ({ 'm.id': `${i}`, 'm.text': o })),