From 2d620055e574f41f466ae13b88cfb0aa77ede068 Mon Sep 17 00:00:00 2001 From: Arjun Mahanti Date: Thu, 10 Sep 2026 11:26:12 -0700 Subject: [PATCH] Keep Pulse on a single combined conversation layout Signed-off-by: Arjun Mahanti --- desktop/src/app/routes/pulse.tsx | 7 - desktop/src/features/pulse/README.md | 17 +- .../features/pulse/lib/workspaceNavigation.ts | 4 +- .../features/pulse/ui/PulseCombinedView.tsx | 22 +-- .../pulse/ui/PulseConversationSplitView.tsx | 141 +++++++-------- .../features/pulse/ui/PulseVariationMenu.tsx | 80 --------- .../features/pulse/ui/PulseWindowActions.tsx | 38 +++++ .../features/pulse/ui/PulseWorkspacePage.tsx | 6 +- .../features/pulse/ui/UnifiedPulseView.tsx | 18 +- desktop/tests/e2e/pulse-unified.spec.ts | 161 ++++++------------ 10 files changed, 169 insertions(+), 325 deletions(-) delete mode 100644 desktop/src/features/pulse/ui/PulseVariationMenu.tsx create mode 100644 desktop/src/features/pulse/ui/PulseWindowActions.tsx diff --git a/desktop/src/app/routes/pulse.tsx b/desktop/src/app/routes/pulse.tsx index 1b4697bad0..6a38f43b48 100644 --- a/desktop/src/app/routes/pulse.tsx +++ b/desktop/src/app/routes/pulse.tsx @@ -28,7 +28,6 @@ type PulseRouteSearch = ReturnType & { view?: "create" | "edit" | "duplicate"; pane?: string; feed?: string; - layout?: "combined" | "separate"; conversation?: string; dm?: string; channel?: string; @@ -75,12 +74,6 @@ function validatePulseSearch( ].includes(String(search.feed)) ? String(search.feed) : undefined, - layout: - search.layout === "separate" - ? "separate" - : search.layout === "combined" - ? "combined" - : undefined, conversation: stringValue("conversation"), dm: stringValue("dm"), channel: stringValue("channel"), diff --git a/desktop/src/features/pulse/README.md b/desktop/src/features/pulse/README.md index 9d040d1eb0..fd6ff8eb85 100644 --- a/desktop/src/features/pulse/README.md +++ b/desktop/src/features/pulse/README.md @@ -29,15 +29,14 @@ highlight opens Search filtered to those conversations. Failures expose a retry than pretending an excerpt is an AI summary. Browser tests mock the model endpoint; live checks require the user-approved Codex connection. -The ellipsis menu at the top right of the window switches between **Separate feeds** -and **Combined conversations**. The latter has no top tabs: Search and For you -appear as circular-icon rows above All messages in the persistent sidebar, and -Agents is hidden. All messages opens the mixed DM and channel feed above -a single 220px list of joined DMs and channels, sorted by the -newest relay timestamp or loaded message. Selection is independent of list order, -so incoming activity does not switch the open conversation. The variation and -selected conversation live in the URL and survive reloads; both variations use -the same conversation detail and draft storage within the 960px container. +Messages uses a single combined conversation layout. Search and For you appear +as circular-icon rows above All messages in the persistent sidebar. All messages +opens the mixed DM and channel feed beside a 220px list of joined DMs and channels, +sorted by the newest relay timestamp or loaded message. Selection is independent +of list order, so incoming activity does not switch the open conversation. The +selected conversation lives in the URL and survives reloads; conversation detail +and draft storage stay within the 960px container. Older variation links open +this same combined layout. Conversation detail uses message bubbles: incoming messages align left in gray, and messages authored by the signed-in viewer align right in the selected theme’s accent color, with its matching contrast text. This is scoped diff --git a/desktop/src/features/pulse/lib/workspaceNavigation.ts b/desktop/src/features/pulse/lib/workspaceNavigation.ts index 51b2a59236..efc4584010 100644 --- a/desktop/src/features/pulse/lib/workspaceNavigation.ts +++ b/desktop/src/features/pulse/lib/workspaceNavigation.ts @@ -27,10 +27,9 @@ export const CLEAR_WORKSPACE_PANELS = Object.fromEntries( PULSE_WORKSPACE_KEYS.map((key) => [key, null]), ) as Record<(typeof PULSE_WORKSPACE_KEYS)[number], null>; -/** Reuse feature navigation while retaining Pulse's rail and selected variation. */ +/** Reuse feature navigation while retaining Pulse's rail and selected conversation. */ export function workspaceNavigationTarget( target: AppNavigationTarget, - layout: string | null, conversation: string | null, ): AppNavigationTarget { let feed: PulseView; @@ -67,7 +66,6 @@ export function workspaceNavigationTarget( to: "/pulse", params: undefined, search: { - layout: layout ?? undefined, conversation: conversation ?? undefined, ...target.search, feed, diff --git a/desktop/src/features/pulse/ui/PulseCombinedView.tsx b/desktop/src/features/pulse/ui/PulseCombinedView.tsx index 9a940b6c74..c53eeecedd 100644 --- a/desktop/src/features/pulse/ui/PulseCombinedView.tsx +++ b/desktop/src/features/pulse/ui/PulseCombinedView.tsx @@ -6,7 +6,6 @@ import { sortConversationsByRecency } from "../lib/conversationRecency"; import { PulseConversationSplitView } from "./PulseConversationSplitView"; export function PulseCombinedView({ - grouped = false, channels, conversations, currentPubkey, @@ -15,7 +14,6 @@ export function PulseCombinedView({ view, onSelectView, }: { - grouped?: boolean; channels: Channel[]; conversations: PulseConversation[]; currentPubkey?: string; @@ -24,26 +22,16 @@ export function PulseCombinedView({ view: PulseView; onSelectView: (view: PulseView) => void; }) { - const sorted = React.useMemo(() => { - const recent = sortConversationsByRecency(channels, conversations); - return grouped - ? [ - ...recent.filter((channel) => channel.channelType === "dm"), - ...recent.filter((channel) => channel.channelType !== "dm"), - ] - : recent; - }, [channels, conversations, grouped]); + const sorted = React.useMemo( + () => sortConversationsByRecency(channels, conversations), + [channels, conversations], + ); return ( )} {visibleRows.length ? ( - visibleRows.map((row, index) => { + visibleRows.map((row) => { const participant = row.participants[0]; const working = row.channel.channelType === "dm" && @@ -212,81 +210,70 @@ export function PulseConversationSplitView({ ); const unread = isUnread(row.channel.id); return ( - - {grouped && - index > 0 && - (visibleRows[index - 1].channel.channelType === "dm") !== - (row.channel.channelType === "dm") && ( -