Skip to content
Closed
2 changes: 2 additions & 0 deletions desktop/playwright.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,8 @@ export default defineConfig({
"**/thread-reply-anchor-roleplay.spec.ts",
"**/threadpane-ultrawide.spec.ts",
"**/thread-focus-mode.spec.ts",
"**/agent-activity-cover.spec.ts",
"**/agent-activity-cover-screenshots.spec.ts",
"**/animated-avatar.spec.ts",
"**/reminders.spec.ts",
"**/reminder-click-repro.spec.ts",
Expand Down
4 changes: 2 additions & 2 deletions desktop/src/app/AppShell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ import {
import { useDueReminderBadgeCount } from "@/features/reminders/hooks";
import { useReminderNotifications } from "@/features/reminders/useReminderNotifications";
import { AppSidebar } from "@/features/sidebar/ui/AppSidebar";
import { requestFocusedThreadClose } from "@/features/channels/focusedThreadCloseRequest";
import { requestCoverDrawerClose } from "@/features/channels/coverDrawerCloseRequest";
import { CommunityRail } from "@/features/sidebar/ui/CommunityRail";
import { useChannelMutes } from "@/features/sidebar/lib/useChannelMutes";
import { useChannelStars } from "@/features/sidebar/lib/useChannelStars";
Expand Down Expand Up @@ -846,7 +846,7 @@ export function AppShell() {
addCommunityDialog.onOpenChange
}
onNewMessage={goNewMessage}
onBackgroundClick={requestFocusedThreadClose}
onBackgroundClick={requestCoverDrawerClose}
onCreateChannelOpenChange={setIsCreateChannelOpen}
onOpenAddCommunity={addCommunityDialog.openDialog}
onSendFeedback={() => setIsSendFeedbackOpen(true)}
Expand Down
21 changes: 21 additions & 0 deletions desktop/src/features/channels/coverDrawerCloseRequest.test.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import assert from "node:assert/strict";
import test from "node:test";

import {
requestCoverDrawerClose,
subscribeToCoverDrawerCloseRequest,
} from "./coverDrawerCloseRequest.ts";

test("cover drawer close requests reach active subscribers only", () => {
let calls = 0;
const unsubscribe = subscribeToCoverDrawerCloseRequest(() => {
calls += 1;
});

requestCoverDrawerClose();
assert.equal(calls, 1);

unsubscribe();
requestCoverDrawerClose();
assert.equal(calls, 1);
});
22 changes: 22 additions & 0 deletions desktop/src/features/channels/coverDrawerCloseRequest.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
const listeners = new Set<() => void>();

/**
* Request dismissal of the channel's open cover drawer.
*
* One channel of a channel pane is covered at a time (focus-mode thread or
* agent activity), so this needs no discriminator — whichever drawer is open
* subscribes and closes.
*/
export function requestCoverDrawerClose(): void {
for (const listener of listeners) {
listener();
}
}

/** Subscribe the active cover drawer to external dismissal requests. */
export function subscribeToCoverDrawerCloseRequest(
listener: () => void,
): () => void {
listeners.add(listener);
return () => listeners.delete(listener);
}

This file was deleted.

16 changes: 0 additions & 16 deletions desktop/src/features/channels/focusedThreadCloseRequest.ts

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import assert from "node:assert/strict";
import test from "node:test";

import { getAgentSessionPanelPresentation } from "./agentSessionPanelPresentation.ts";

test("the cover drawer owns motion and gets standalone, opaque chrome", () => {
assert.deepEqual(
getAgentSessionPanelPresentation({
isCoverDrawer: true,
isSinglePanelView: false,
useSplitAuxiliaryPane: true,
}),
{
enterMotion: false,
isSinglePanelView: true,
layout: "standalone",
transparentChrome: false,
},
);
});

test("the split pane keeps docked chrome and its own enter motion", () => {
assert.deepEqual(
getAgentSessionPanelPresentation({
isCoverDrawer: false,
isSinglePanelView: false,
useSplitAuxiliaryPane: true,
}),
{
enterMotion: true,
isSinglePanelView: false,
layout: "split",
transparentChrome: true,
},
);
});

test("narrow viewports keep today's overlay and single-panel presentations", () => {
for (const isSinglePanelView of [false, true]) {
assert.deepEqual(
getAgentSessionPanelPresentation({
isCoverDrawer: false,
isSinglePanelView,
useSplitAuxiliaryPane: false,
}),
{
enterMotion: true,
isSinglePanelView,
layout: "standalone",
transparentChrome: false,
},
);
}
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/**
* `AnimatePresence` key shared by every agent activity presentation.
*
* The split pane and the cover drawer are two containers for one session, so
* presence is a property of the session, not of either container — crossing the
* viewport breakpoint changes how it is shown, not whether it is open.
*/
export const AGENT_SESSION_SURFACE_KEY = "agent-session-surface";

export type AgentSessionPanelPresentation = {
enterMotion: boolean;
isSinglePanelView: boolean;
layout: "standalone" | "split";
transparentChrome: boolean;
};

type AgentSessionPanelPresentationOptions = {
/** The panel is rendered inside the agent activity cover drawer. */
isCoverDrawer: boolean;
isSinglePanelView: boolean;
useSplitAuxiliaryPane: boolean;
};

/**
* Maps channel presentation into the agent session panel's layout props.
*
* TODO(#6538): once the `conversation` transcript variant lands on main, this
* should also return `transcriptVariant: "conversation"` for the cover drawer
* and `undefined` otherwise, so the reading view is pinned by presentation
* rather than inferred from panel width. The variant does not exist on main
* yet, so the prop is deliberately not set here.
*/
export function getAgentSessionPanelPresentation({
isCoverDrawer,
isSinglePanelView,
useSplitAuxiliaryPane,
}: AgentSessionPanelPresentationOptions): AgentSessionPanelPresentation {
if (isCoverDrawer) {
return {
// The drawer animates itself; a second slide inside it would compound.
enterMotion: false,
// Fills the drawer, and selects the standalone header chrome that owns
// its own backdrop — the drawer is not sharing the channel's header, and
// it has no resizable neighbour to draw a resize border against.
isSinglePanelView: true,
layout: "standalone",
transparentChrome: false,
};
}

return {
enterMotion: true,
isSinglePanelView: useSplitAuxiliaryPane ? false : isSinglePanelView,
layout: useSplitAuxiliaryPane ? "split" : "standalone",
transparentChrome: useSplitAuxiliaryPane,
};
}
Loading
Loading