Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions desktop/playwright.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ export default defineConfig({
"**/thread-reply-anchor-roleplay.spec.ts",
"**/threadpane-ultrawide.spec.ts",
"**/thread-focus-mode.spec.ts",
"**/agent-activity-cover.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