diff --git a/src/renderer/components/team/messages/MessageComposer.tsx b/src/renderer/components/team/messages/MessageComposer.tsx index ef75fd1f..f8f1616b 100644 --- a/src/renderer/components/team/messages/MessageComposer.tsx +++ b/src/renderer/components/team/messages/MessageComposer.tsx @@ -189,6 +189,7 @@ export const MessageComposer = ({ trimmed.length > 0 && trimmed.length <= MAX_TEXT_LENGTH && !sending && + !isProvisioning && !attachmentsBlocked && (!isCrossTeam || onCrossTeamSend !== undefined); @@ -621,7 +622,7 @@ export const MessageComposer = ({ minRows={2} maxRows={6} maxLength={MAX_TEXT_LENGTH} - disabled={sending} + disabled={sending || isProvisioning} cornerAction={
{/* NOTE: ContextRing disabled — usage formula is inaccurate */} diff --git a/src/renderer/hooks/useMarkCommentsRead.ts b/src/renderer/hooks/useMarkCommentsRead.ts index 474067f9..d9c9160e 100644 --- a/src/renderer/hooks/useMarkCommentsRead.ts +++ b/src/renderer/hooks/useMarkCommentsRead.ts @@ -1,61 +1,40 @@ -import { useCallback, useEffect, useRef } from 'react'; +import { useEffect, useRef } from 'react'; import { markAsRead } from '@renderer/services/commentReadStorage'; import type { TaskComment } from '@shared/types'; +/** + * Marks task comments as read when the component is mounted and + * whenever the comments list changes while mounted. + * + * Previously used IntersectionObserver, but since the component + * is only rendered inside a CollapsibleTeamSection (conditional + * mount/unmount controls visibility), a simple effect is both + * simpler and more reliable — especially inside Dialog portals + * where IntersectionObserver can miss the initial intersection. + * + * Returns a ref callback for the comments container (kept for + * API compatibility with TaskCommentsSection). + */ export function useMarkCommentsRead( teamName: string, taskId: string, comments: TaskComment[] ): (node: HTMLElement | null) => void { - const isVisibleRef = useRef(false); - const observerRef = useRef(null); + const nodeRef = useRef(null); - // Mark comments as read if section is visible - const markIfVisible = useCallback(() => { - if (!isVisibleRef.current || comments.length === 0) return; + // Mark as read on mount and whenever comments change + useEffect(() => { + if (comments.length === 0) return; const latest = Math.max(...comments.map((c) => new Date(c.createdAt).getTime())); if (latest > 0) markAsRead(teamName, taskId, latest); }, [teamName, taskId, comments]); - // Re-mark when new comments arrive while section is visible - useEffect(() => { - markIfVisible(); - }, [markIfVisible]); - - // IntersectionObserver ref callback - const refCallback = useCallback( - (node: HTMLElement | null) => { - // Cleanup previous - if (observerRef.current) { - observerRef.current.disconnect(); - observerRef.current = null; - } - - if (!node) { - isVisibleRef.current = false; - return; - } - - observerRef.current = new IntersectionObserver( - ([entry]) => { - isVisibleRef.current = entry.isIntersecting; - if (entry.isIntersecting) markIfVisible(); - }, - { threshold: 0.1 } - ); - observerRef.current.observe(node); - }, - [markIfVisible] - ); - - // Cleanup on unmount - useEffect(() => { - return () => { - observerRef.current?.disconnect(); - }; - }, []); + // Stable ref callback (no dependencies — just stores the node) + const refCallback = useRef((node: HTMLElement | null) => { + nodeRef.current = node; + }).current; return refCallback; }