diff --git a/ui/src/components/PropertiesPanel.tsx b/ui/src/components/PropertiesPanel.tsx index 41cf5a5f83e..926927c3903 100644 --- a/ui/src/components/PropertiesPanel.tsx +++ b/ui/src/components/PropertiesPanel.tsx @@ -47,6 +47,19 @@ export function PropertiesPanel() { * variant. Everything below renders only when the flag is ON. * ------------------------------------------------------------------------- */ +/** + * Portal target in the redesigned pane's header bar: hosted content (the + * Properties | Plan | Artifacts tab strip) renders here, left of the window + * controls. See IssueProperties' flag-ON shell. + */ +export const PROPERTIES_PANE_HEADER_SLOT_ID = "properties-pane-header-slot"; +/** + * Portal target pinned below the pane's scroll area: hosted content (the plan + * confirmation action bar) renders here so it stays visible while the pane + * body scrolls. + */ +export const PROPERTIES_PANE_FOOTER_SLOT_ID = "properties-pane-footer-slot"; + const WIDTH_STORAGE_KEY = "taskChatRedesign.propertiesPaneWidth"; const DEFAULT_PANE_WIDTH = 322; const MIN_PANE_WIDTH = 260; @@ -340,9 +353,13 @@ function ResizablePropertiesPanel({ className={cn("flex-1 flex flex-col min-h-0", isFixed && "w-full")} style={isFixed ? undefined : { width, minWidth: width }} > - {/* No pane title: the tab strip below (Properties/…) already names the - surface — this row is just the window controls. */} -
+ {/* No pane title: the tab strip (portaled into the slot by + IssueProperties) names the surface; window controls sit right. */} +
+
+
+ ) : null; + return (
- {hasOlderComments ? ( -
- -
- ) : null} + {/* Redesign: the button rides inside the thread's scroll viewport with the + header so nothing sits above the thread in the page flow. */} + {taskChatRedesignEnabled ? null : loadOlderButton} + {threadHeader} + {loadOlderButton} + + ) : undefined + } comments={commentsWithRunMeta} interactions={interactions} feedbackVotes={feedbackVotes} @@ -1563,6 +1582,9 @@ export function IssueDetail() { const [mobilePropsOpen, setMobilePropsOpen] = useState(false); const [fileViewerPromptOpen, setFileViewerPromptOpen] = useState(false); const [detailTab, setDetailTab] = useState("chat"); + // Redesign: the center tab strip is hidden, so chat is the only surface — + // deep links that would switch tabs (e.g. #document- hashes) stay on chat. + const resolvedDetailTab = taskChatShellEnabled ? "chat" : detailTab; const [handoffFocusSignal, setHandoffFocusSignal] = useState(0); const [pendingApprovalAction, setPendingApprovalAction] = useState<{ approvalId: string; @@ -4084,17 +4106,12 @@ export function IssueDetail() { ); - return ( - -
- {/* Parent chain breadcrumb */} - {ancestors.length > 0 && ( + // Task Chat Redesign ("not sticky" header): the parent breadcrumb, the + // title/badge block, and the plugin toolbars render INSIDE the thread's + // scroll viewport, so they scroll away with the messages and the composer + // stays near the viewport bottom. Flag OFF renders the same nodes in the + // page flow, in their original order relative to the alert banners. + const ancestorsNav = ancestors.length > 0 && ( - )} - - {issue.hiddenAt && ( -
- - This task is hidden -
- )} - {activePauseHold && ( -
- {activePauseHold.isRoot ? ( -
-
- - {childIssues.length === 0 ? "Paused by board." : "Subtree pause is active."} - - - {childIssues.length === 0 - ? "Task execution is held until resume. Human comments can still wake the assignee for triage." - : "Root and descendant execution is held until resume. Human comments can still wake assignee agents for triage."} - -
-
- {childIssues.length === 0 - ? "1 task held" - : `${heldDescendantCount} descendant${heldDescendantCount === 1 ? "" : "s"} held`} - {activeRootPauseHold?.createdAt ? ` · started ${relativeTime(activeRootPauseHold.createdAt)}` : ""} -
- {canShowSubtreeControls || canResumeLeafWork ? ( -
- - - {canShowSubtreeControls ? ( - - ) : null} -
- ) : null} -
- ) : ( -
- This task is paused by ancestor{" "} - {activePauseHoldRoot?.identifier ? ( - - {activePauseHoldRoot.identifier} - - ) : ( - activePauseHold.rootIssueId.slice(0, 8) - )} - . Resume from the root task to deliver deferred work. -
- )} -
- )} + ); + const issueHeaderBlock = (
)}
+ ); + const pluginOutletsBlock = ( + <> + + ); + + const taskChatThreadHeader = taskChatShellEnabled ? ( + <> + {ancestorsNav} + {issueHeaderBlock} + {pluginOutletsBlock} + + ) : undefined; + + return ( + +
+ {/* Parent chain breadcrumb (redesign: rendered inside the thread viewport) */} + {taskChatShellEnabled ? null : ancestorsNav} + + {issue.hiddenAt && ( +
+ + This task is hidden +
+ )} + {activePauseHold && ( +
+ {activePauseHold.isRoot ? ( +
+
+ + {childIssues.length === 0 ? "Paused by board." : "Subtree pause is active."} + + + {childIssues.length === 0 + ? "Task execution is held until resume. Human comments can still wake the assignee for triage." + : "Root and descendant execution is held until resume. Human comments can still wake assignee agents for triage."} + +
+
+ {childIssues.length === 0 + ? "1 task held" + : `${heldDescendantCount} descendant${heldDescendantCount === 1 ? "" : "s"} held`} + {activeRootPauseHold?.createdAt ? ` · started ${relativeTime(activeRootPauseHold.createdAt)}` : ""} +
+ {canShowSubtreeControls || canResumeLeafWork ? ( +
+ + + {canShowSubtreeControls ? ( + + ) : null} +
+ ) : null} +
+ ) : ( +
+ This task is paused by ancestor{" "} + {activePauseHoldRoot?.identifier ? ( + + {activePauseHoldRoot.identifier} + + ) : ( + activePauseHold.rootIssueId.slice(0, 8) + )} + . Resume from the root task to deliver deferred work. +
+ )} +
+ )} + + {taskChatShellEnabled ? null : issueHeaderBlock} + + {taskChatShellEnabled ? null : pluginOutletsBlock} {taskChatShellEnabled ? null : showRichSubIssuesSection ? (
@@ -4760,9 +4807,12 @@ export function IssueDetail() { ); })()} - + {taskChatShellEnabled ? null : } - + + {/* Redesign: the chat IS the page — the Chat/Activity/Related-work tab + strip is hidden and the thread renders as the only surface. */} + {taskChatShellEnabled ? null : ( @@ -4782,13 +4832,15 @@ export function IssueDetail() { ))} + )} {/* Flag ON the thread viewport extends under main's horizontal padding (symmetric, so the centered column keeps the same axis) and the scrollbar sits flush against the properties-pane border. */} - {detailTab === "chat" ? ( + {resolvedDetailTab === "chat" ? (