From 8cea5e15efa524d2706c228359e1feedb4f6ef03 Mon Sep 17 00:00:00 2001 From: ss-dev-00 Date: Wed, 26 Aug 2026 15:37:40 -0700 Subject: [PATCH] refactor(desktop): extract the channel idle auxiliary surface Landing the cover drawer on current main pushed `ChannelPane.tsx` to 1003 lines, three over the 1000-line cap. The file was already over the cap on the cover drawer's original base (1010 lines, so shrinking was permitted), but main has since brought it down to 982, so it may not grow at all. Move the caller-owned idle auxiliary surface into its own component, the same split `ChannelAgentSessionSurface` already uses, which brings the pane back to 996. Behaviour-preserving: the panel and both of its presentations are byte-equivalent to the inline version, and the per-presentation React keys stay on `AnimatePresence`'s direct child rather than moving inside the new component, so the cover drawer and the split pane keep the distinct enter/exit identities main gave them. Co-authored-by: Bradley Axen Signed-off-by: Bradley Axen Signed-off-by: ss-dev-00 --- .../ui/ChannelIdleAuxiliarySurface.tsx | 86 +++++++++++++++++++ .../src/features/channels/ui/ChannelPane.tsx | 58 ++++++------- 2 files changed, 112 insertions(+), 32 deletions(-) create mode 100644 desktop/src/features/channels/ui/ChannelIdleAuxiliarySurface.tsx diff --git a/desktop/src/features/channels/ui/ChannelIdleAuxiliarySurface.tsx b/desktop/src/features/channels/ui/ChannelIdleAuxiliarySurface.tsx new file mode 100644 index 0000000000..5263e86e1f --- /dev/null +++ b/desktop/src/features/channels/ui/ChannelIdleAuxiliarySurface.tsx @@ -0,0 +1,86 @@ +import type * as React from "react"; + +import { FocusThreadDrawer } from "@/features/channels/ui/FocusThreadDrawer"; +import { + IdleAuxiliaryPanel, + type IdleAuxiliaryHeaderControls, +} from "@/features/channels/ui/IdleAuxiliaryPanel"; + +type ChannelIdleAuxiliarySurfaceProps = { + canResetWidth: boolean; + channelName: string; + children: React.ReactNode; + headerControls?: IdleAuxiliaryHeaderControls; + /** Render the panel inside the cover drawer instead of the split pane. */ + isCoverDrawer: boolean; + isSinglePanelView: boolean; + onClose: () => void; + onResetWidth: () => void; + onResizeStart: React.PointerEventHandler; + title: string; + useSplitAuxiliaryPane: boolean; + widthPx: number; + /** + * Applies the split-pane presentation, including its resize affordances. + * Supplied by `ChannelPane` because that pane owns the resize state; the + * cover-drawer presentation is applied here. + */ + wrapSplitPane: (panel: React.ReactNode) => React.ReactNode; +}; + +/** + * The channel's caller-owned idle auxiliary surface, in whichever presentation + * was resolved for it. + * + * Split out of `ChannelPane` alongside `ChannelAgentSessionSurface` so each + * auxiliary surface owns its own presentation wiring rather than adding another + * pair of closures to that component. + * + * It reuses `FocusThreadDrawer` rather than `CoverDrawer` directly, so the two + * cover presentations stay one surface with one test id; only the accessible + * label differs, which is why that label is a prop on the thread drawer. + */ +export function ChannelIdleAuxiliarySurface({ + canResetWidth, + channelName, + children, + headerControls, + isCoverDrawer, + isSinglePanelView, + onClose, + onResetWidth, + onResizeStart, + title, + useSplitAuxiliaryPane, + widthPx, + wrapSplitPane, +}: ChannelIdleAuxiliarySurfaceProps) { + const panel = ( + + {children} + + ); + + return isCoverDrawer ? ( + + {panel} + + ) : ( + wrapSplitPane(panel) + ); +} diff --git a/desktop/src/features/channels/ui/ChannelPane.tsx b/desktop/src/features/channels/ui/ChannelPane.tsx index 8b8a3769c1..cf25d9a4e5 100644 --- a/desktop/src/features/channels/ui/ChannelPane.tsx +++ b/desktop/src/features/channels/ui/ChannelPane.tsx @@ -27,10 +27,10 @@ import { buildVideoReviewPresentationByMessageId } from "@/features/messages/lib import { useComposerHeightPadding } from "@/features/messages/ui/useComposerHeightPadding"; import { UserProfilePanel } from "@/features/profile/ui/UserProfilePanel"; import { ChannelManagementAuxiliaryPanel } from "@/features/channels/ui/ChannelManagementAuxiliaryPanel"; -import { IdleAuxiliaryPanel } from "@/features/channels/ui/IdleAuxiliaryPanel"; import { RightAuxiliaryPane } from "@/features/channels/ui/RightAuxiliaryPane"; import { ThreadViewModeToggle } from "@/features/channels/ui/ThreadViewModeToggle"; import { ChannelAgentSessionSurface } from "@/features/channels/ui/ChannelAgentSessionSurface"; +import { ChannelIdleAuxiliarySurface } from "@/features/channels/ui/ChannelIdleAuxiliarySurface"; import { FocusThreadDrawer } from "@/features/channels/ui/FocusThreadDrawer"; import { AGENT_SESSION_SURFACE_KEY } from "@/features/channels/lib/agentSessionPanelPresentation"; import { @@ -540,38 +540,32 @@ export const ChannelPane = React.memo(function ChannelPane({ ) : ( wrapAux(panel, "message-thread-panel", { key: THREAD_SURFACE_KEY }) ); - const wrapIdlePanel = (panel: React.ReactNode) => - useFocusIdleDrawer && onCloseIdleAuxiliaryPanel ? ( - - {panel} - - ) : ( - wrapAux(panel, "idle-auxiliary-panel") - ); const idleAuxiliarySurface = - idleAuxiliaryPanel && onCloseIdleAuxiliaryPanel - ? wrapIdlePanel( - - {idleAuxiliaryPanel} - , - ) - : null; + idleAuxiliaryPanel && onCloseIdleAuxiliaryPanel ? ( + wrapAux(panel, "idle-auxiliary-panel")} + > + {idleAuxiliaryPanel} + + ) : null; const wrapAgentSessionSplitPane = (panel: React.ReactNode) => wrapAux(panel, "agent-session-thread-panel", { key: AGENT_SESSION_SURFACE_KEY,