From 6ccde2b0d815f6923e738a5e46222481fc214eb6 Mon Sep 17 00:00:00 2001 From: gsxdsm Date: Wed, 5 Aug 2026 13:02:48 -0700 Subject: [PATCH] FN-8806: stabilize task title expansion during resize Keep expanded task titles stable while their container resizes. - Measure persistent title text separately from the interactive expansion control. - Fence hidden pop-out resize observers and preserve expansion state across detail hosts. - Add rendering and browser coverage for resize-driven title behavior. Files changed: .changeset/fn-8806-task-title-flicker.md | 7 + .../dashboard/app/components/TaskDetailModal.css | 16 +- .../dashboard/app/components/TaskDetailModal.tsx | 37 +-- .../__tests__/TaskDetailModal.rendering.test.tsx | 42 +++- .../app/task-modal-touch-resize-e2e-fixture.tsx | 149 +++++++++++- .../task-modal-touch-resize-browser.test.ts | 267 ++++++++++++++++++++- 6 files changed, 485 insertions(+), 33 deletions(-) Fusion-Task-Id: FN-8806 Fusion-Task-Lineage: 9519a5dc-ae6d-43e1-a0da-7ac963f27d7f Co-authored-by: Fusion (runfusion.ai) --- .changeset/fn-8806-task-title-flicker.md | 7 + .../app/components/TaskDetailModal.css | 16 +- .../app/components/TaskDetailModal.tsx | 37 +-- .../TaskDetailModal.rendering.test.tsx | 42 ++- .../task-modal-touch-resize-e2e-fixture.tsx | 149 +++++++++- .../task-modal-touch-resize-browser.test.ts | 267 +++++++++++++++++- 6 files changed, 485 insertions(+), 33 deletions(-) create mode 100644 .changeset/fn-8806-task-title-flicker.md 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 && (