diff --git a/.changeset/fn-8806-task-title-flicker.md b/.changeset/fn-8806-task-title-flicker.md new file mode 100644 index 0000000000..cfd070b552 --- /dev/null +++ b/.changeset/fn-8806-task-title-flicker.md @@ -0,0 +1,7 @@ +--- +"@runfusion/fusion": patch +--- + +summary: Keep expanded task titles stable without flickering during resize. +category: fix +dev: Measures the persistent title text box so its control cannot alter overflow eligibility. diff --git a/packages/dashboard/app/components/TaskDetailModal.css b/packages/dashboard/app/components/TaskDetailModal.css index bc6268fd7f..fbd54b94fe 100644 --- a/packages/dashboard/app/components/TaskDetailModal.css +++ b/packages/dashboard/app/components/TaskDetailModal.css @@ -205,9 +205,19 @@ the previous direct-child flex contract so the transcript and agent log still fi margin-bottom: var(--space-md); overflow-wrap: anywhere; word-break: break-word; + position: relative; } -.detail-title--collapsed { +/* +FNXC:TaskDetailTitle 2026-08-05-17:54: +The measured text stays in flow while the interactive overlay changes state, so eligibility has +one geometry in every task-detail host instead of depending on whether its button is present. +*/ +.detail-title-measurement { + display: block; +} + +.detail-title--collapsed .detail-title-measurement { display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; @@ -236,10 +246,12 @@ Summarize-as-title is an in-field affordance, not a separate full-width row: it .detail-title-control { display: block; width: 100%; + position: absolute; + inset: 0; padding: 0; border: 0; background: transparent; - color: inherit; + color: transparent; font: inherit; text-align: inherit; cursor: pointer; diff --git a/packages/dashboard/app/components/TaskDetailModal.tsx b/packages/dashboard/app/components/TaskDetailModal.tsx index 761767fea1..2ada0ed57d 100644 --- a/packages/dashboard/app/components/TaskDetailModal.tsx +++ b/packages/dashboard/app/components/TaskDetailModal.tsx @@ -1207,17 +1207,18 @@ export function TaskDetailContent({ const [highlightStallCode, setHighlightStallCode] = useState(null); const [descriptionExpanded, setDescriptionExpanded] = useState(false); const [titleOverflows, setTitleOverflows] = useState(false); - const titleRef = useRef(null); + const titleRef = useRef(null); const displayTitleText = task.title || task.description || task.id; /* - FNXC:TaskDetailTitle 2026-08-05-16:42: - Title overflow eligibility belongs to the stable two-line collapsed layout, while expanded or - collapsed display is an explicit operator choice. Do not mutate the live expanded heading to - measure it: ResizeObserver callbacks caused by that class change can otherwise replace the - title-owned control and flicker the detail view. Reset both values before paint only when this - task identity or its displayed title/fallback changes; ordinary rerenders and resize callbacks - must never reverse the choice. + FNXC:TaskDetailTitle 2026-08-05-18:48: + Browser layout proved that swapping bare heading text for the semantic title button can alter the + exact box whose overflow decides whether that button exists. Measure an always-present text span + with the collapsed two-line rules instead; the button is an out-of-flow accessible overlay and + cannot feed back into eligibility. Expansion remains an operator-owned state across modal, + full-panel, split-pane, right-dock, and floating-window hosts. Reset only for a new task or its + title/description/id fallback, never from a resize delivery. Kept-alive hidden pop-outs are not a + live layout authority: disconnect and fence their callbacks until their host is visible again. */ useLayoutEffect(() => { setDescriptionExpanded(false); @@ -1281,6 +1282,10 @@ export function TaskDetailContent({ const [prCreateOpen, setPrCreateOpen] = useState(false); useLayoutEffect(() => { + // A kept-alive floating detail can be hidden while another view owns its layout. Its stale + // ResizeObserver delivery must not change eligibility before the host becomes visible again. + if (!active) return; + const titleElement = titleRef.current; if (!titleElement) { setTitleOverflows(false); @@ -1311,7 +1316,7 @@ export function TaskDetailContent({ resizeObserver?.disconnect(); window.removeEventListener("resize", measureTitleOverflow); }; - }, [descriptionExpanded, displayTitleText, task.id]); + }, [active, descriptionExpanded, displayTitleText, task.id]); /* FNXC:WorkflowBadges 2026-06-29-00:00: @@ -4797,10 +4802,10 @@ export function TaskDetailContent({ FNXC:TaskDetailTitle 2026-08-04-18:00: An overflowing task-detail title is its own sole expansion control. Keep the semantic button inside the h2 so pointer, touch, and keyboard activation share one accessible target; the separate Show more/Show less row must not return. */} -

+

+ + {displayTitleText} + {titleOverflows || descriptionExpanded ? ( - ) : displayTitleText} + /> + ) : null}

{showSummarizeTitleButton && (