diff --git a/.changeset/planning-single-add-comment-trigger.md b/.changeset/planning-single-add-comment-trigger.md new file mode 100644 index 0000000000..73fd33472a --- /dev/null +++ b/.changeset/planning-single-add-comment-trigger.md @@ -0,0 +1,7 @@ +--- +"@runfusion/fusion": patch +--- + +summary: Planning Mode shows one "Add comment to selection" button, and only once the selection is finished. +category: fix +dev: Removes the `planning-add-comment--document` trigger and the `--mobile` modifier (single `.planning-add-comment` rail button at every breakpoint); `planSelectionDragActiveRef` suppresses quote writes between pointerdown and pointerup inside the plan document so mid-drag `selectionchange` no longer mounts/unmounts the trigger. diff --git a/packages/dashboard/app/components/PlanningModeModal.css b/packages/dashboard/app/components/PlanningModeModal.css index 0d605519cd..574769b637 100644 --- a/packages/dashboard/app/components/PlanningModeModal.css +++ b/packages/dashboard/app/components/PlanningModeModal.css @@ -1798,15 +1798,17 @@ Contextual comments remain transient review controls: render the capture/editor selection, and render the tray only after a valid comment exists so an empty plan review keeps its established Refine and Proceed action hierarchy. */ +/* +FNXC:PlanningComments 2026-07-25-10:20: +Single Add-comment trigger, living in the plan action rail at every breakpoint. The former +--document / --mobile variant pair is gone: both rendered in the DOM and read as duplicate buttons. +*/ .planning-add-comment { display: inline-flex; align-items: center; + justify-content: center; gap: var(--space-sm); - margin-top: var(--space-lg); -} - -.planning-add-comment--mobile { - display: none; + margin-top: 0; } .planning-comment-editor, @@ -1910,17 +1912,11 @@ reach past Proceed — match the tablet in-flow stack on phone too. } /* - FNXC:PlanningComments 2026-07-24-05:35: - Tablet and phone hide the document-adjacent trigger (it sits at the end of a long plan and - requires scrolling). The action-rail variant becomes the sole control and spans the full - footer width above Refine/Proceed so a selection always exposes Add comment without leaving - the plan action baseline. + FNXC:PlanningComments 2026-07-25-10:20: + Tablet and phone give the sole action-rail trigger the full footer width above Refine/Proceed so a + selection always exposes Add comment without leaving the plan action baseline. */ - .planning-add-comment--document { - display: none; - } - - .planning-plan-actions .btn.planning-add-comment--mobile { + .planning-plan-actions .btn.planning-add-comment { display: flex; grid-column: 1 / -1; margin-top: 0; @@ -1934,7 +1930,7 @@ reach past Proceed — match the tablet in-flow stack on phone too. tablet and phone share the mobile icon scale (default lucide size can dwarf the label in the full-width rail row). */ - .planning-plan-actions .btn.planning-add-comment--mobile svg { + .planning-plan-actions .btn.planning-add-comment svg { width: var(--space-lg); height: var(--space-lg); flex-shrink: 0; @@ -2089,7 +2085,7 @@ plan actions, and a token-sized bottom inset keep all three controls inline with gap: var(--space-md); } - .planning-plan-actions .btn.planning-add-comment--mobile { + .planning-plan-actions .btn.planning-add-comment { display: flex; grid-column: 1 / -1; width: 100%; @@ -2098,7 +2094,7 @@ plan actions, and a token-sized bottom inset keep all three controls inline with gap: var(--space-sm); } - .planning-plan-actions .btn.planning-add-comment--mobile svg { + .planning-plan-actions .btn.planning-add-comment svg { width: var(--space-lg); height: var(--space-lg); flex-shrink: 0; diff --git a/packages/dashboard/app/components/PlanningModeModal.tsx b/packages/dashboard/app/components/PlanningModeModal.tsx index 11335906f4..9d934b565e 100644 --- a/packages/dashboard/app/components/PlanningModeModal.tsx +++ b/packages/dashboard/app/components/PlanningModeModal.tsx @@ -766,10 +766,17 @@ export function PlanningModeModal({ isOpen, onClose, onTaskCreated, onTasksCreat const commentInputRef = useRef(null); const commentEditorRef = useRef(null); const addCommentTriggerRef = useRef(null); - const mobileAddCommentTriggerRef = useRef(null); const restoreCommentTriggerFocusRef = useRef(false); const isCommentEditorOpenRef = useRef(false); const pendingOpenCommentQuoteRef = useRef(null); + /* + FNXC:PlanningComments 2026-07-25-10:20: + While a pointer drag is extending a plan selection, selectionchange fires on every mouse move. + Mounting/unmounting the Add-comment control on each of those intermediate ranges made the button + strobe under the cursor on desktop. This ref suppresses quote writes for the duration of the drag + so the control appears exactly once, when the selection is done (pointerup/pointercancel). + */ + const planSelectionDragActiveRef = useRef(false); const setCommentEditorOpen = useCallback((open: boolean) => { /* @@ -818,12 +825,12 @@ export function PlanningModeModal({ isOpen, onClose, onTaskCreated, onTasksCreat const focusAddCommentTrigger = useCallback(() => { /* - FNXC:PlanningComments 2026-07-24-05:35: - Tablet and phone both expose the action-rail trigger; only wide desktop uses the document - variant. Match the 1024px CSS gate so focus restore lands on the visible control. + FNXC:PlanningComments 2026-07-25-10:20: + One trigger at every breakpoint. The document-adjacent duplicate that sat at the end of the plan + text was removed (FN operator report: two "Add comment to selection" buttons), so focus restore + always targets the plan action rail control. */ - const usesRailTrigger = window.matchMedia?.("(max-width: 1024px)").matches ?? false; - (usesRailTrigger ? mobileAddCommentTriggerRef : addCommentTriggerRef).current?.focus(); + addCommentTriggerRef.current?.focus(); }, []); /* @@ -834,7 +841,7 @@ export function PlanningModeModal({ isOpen, onClose, onTaskCreated, onTasksCreat if (isCommentEditorOpen || !restoreCommentTriggerFocusRef.current) return; restoreCommentTriggerFocusRef.current = false; queueMicrotask(() => { - if (addCommentTriggerRef.current || mobileAddCommentTriggerRef.current) { + if (addCommentTriggerRef.current) { focusAddCommentTrigger(); } }); @@ -2999,6 +3006,12 @@ export function PlanningModeModal({ isOpen, onClose, onTaskCreated, onTasksCreat as the open gesture collapsed the native range. */ if (isCommentEditorOpenRef.current || pendingOpenCommentQuoteRef.current) return; + /* + FNXC:PlanningComments 2026-07-25-10:20: + Mid-drag ranges are not a finished selection. Skip them entirely; the pointerup handler runs one + final capture so the control shows once the selection is done instead of flickering per movement. + */ + if (planSelectionDragActiveRef.current) return; const selection = window.getSelection(); const root = planDocumentRef.current; @@ -3018,12 +3031,38 @@ export function PlanningModeModal({ isOpen, onClose, onTaskCreated, onTasksCreat setSelectedPlanQuote(quote || null); }, []); + /* + FNXC:PlanningComments 2026-07-25-10:20: + Selection-in-progress gate. A drag that starts inside the plan document hides any stale control and + freezes quote updates until the pointer is released; the release (or cancel) performs the single + capture. Drags that start outside the plan are left alone — their collapse still clears the quote + through the normal selectionchange path. + */ useEffect(() => { + const handlePointerDown = (event: PointerEvent) => { + if (isCommentEditorOpenRef.current || pendingOpenCommentQuoteRef.current) return; + const root = planDocumentRef.current; + if (!root || !root.contains(event.target as Node)) return; + planSelectionDragActiveRef.current = true; + setSelectedPlanQuote(null); + }; + const handlePointerRelease = () => { + if (!planSelectionDragActiveRef.current) return; + planSelectionDragActiveRef.current = false; + capturePlanSelection(); + }; + + document.addEventListener("pointerdown", handlePointerDown); + document.addEventListener("pointerup", handlePointerRelease); + document.addEventListener("pointercancel", handlePointerRelease); document.addEventListener("selectionchange", capturePlanSelection); document.addEventListener("mouseup", capturePlanSelection); document.addEventListener("touchend", capturePlanSelection); document.addEventListener("keyup", capturePlanSelection); return () => { + document.removeEventListener("pointerdown", handlePointerDown); + document.removeEventListener("pointerup", handlePointerRelease); + document.removeEventListener("pointercancel", handlePointerRelease); document.removeEventListener("selectionchange", capturePlanSelection); document.removeEventListener("mouseup", capturePlanSelection); document.removeEventListener("touchend", capturePlanSelection); @@ -3353,20 +3392,13 @@ export function PlanningModeModal({ isOpen, onClose, onTaskCreated, onTasksCreat testId="planning-plan-markdown" /> - {selectedPlanQuote && !isCommentEditorOpen && ( - - )} + {/* + FNXC:PlanningComments 2026-07-25-10:20: + The document-adjacent Add-comment trigger was REMOVED. It duplicated the plan action rail + control (operators saw two "Add comment to selection" buttons) and, sitting at the end of a + long plan, it was the harder of the two to reach. The rail trigger below is now the single + control at every breakpoint. Do not reintroduce a second trigger inside the plan document. + */} {isCommentEditorOpen && openCommentQuote && (
{/* - FNXC:PlanningComments 2026-07-31-00:00: - FN-8533 keeps the selection-adjacent control on wide desktop, but compact shells need a - counterpart that cannot be lost under the document fold. - - FNXC:PlanningComments 2026-07-24-05:50: - On tablet and phone (≤1024) the rail control stays in the plan action footer as a - full-width row above Refine/Proceed so a selection never requires scrolling past the - action baseline. Document-level selectionchange still dismisses it when the selection - collapses. CSS shows exactly one of the two variants; only established 768px/1024px - breakpoint literals are allowed here, while all other dimensions remain design-token based. + FNXC:PlanningComments 2026-07-25-10:20: + The plan action rail holds the ONLY Add-comment trigger, at every breakpoint. It cannot be + lost under the document fold, and a selection never requires scrolling past the action + baseline. Document-level selectionchange still dismisses it when the selection collapses. FNXC:PlanningComments 2026-07-24-05:55: - Tablet must keep the two-column grid (not flex nowrap) so Add comment stays a full-width - first row with the same MessageSquarePlus 16px glyph as phone. + Tablet and phone keep the two-column grid (not flex nowrap) so Add comment stays a full-width + first row above Refine/Proceed with the same MessageSquarePlus 16px glyph. */} {selectedPlanQuote && !isCommentEditorOpen && (