feat(desktop): pin the conversation transcript variant to the cover drawer

Resolves the `TODO(#6538)` the cover drawer landed with. The reading view
is now chosen by presentation instead of inferred from panel width: the
cover drawer is the reading surface, so it returns `conversation`, and
every other host stays on the dense activity feed.

Width was only ever a proxy, and a breakable one — the split pane can be
dragged wide and a narrow overlay can be tall. The presentation that
already decided to cover is the thing that knows this is a reading
surface, so it decides the variant too.

Non-cover hosts return `undefined` rather than `"default"`: the panel
already defaults, and naming it here would imply this function decides the
non-cover variant when the profile panel picks `compactPreview` at its own
call site.

Asserted end-to-end on the transcript's `data-transcript-variant` marker in
the screenshot spec rather than on the prop, so it proves the value
survives the whole path from the resolver through the panel to the
transcript. It lives in that spec because the marker only renders once the
transcript has content, and that is the spec that seeds a turn. Verified
discriminating: reverting the pinned value alone fails the assertion.

Co-authored-by: Bradley Axen <baxen@squareup.com>
Signed-off-by: Bradley Axen <baxen@squareup.com>
This commit is contained in:
ss-dev-00
2026-08-24 11:59:18 -07:00
parent 9631221e54
commit 91d4e93ff1
3 changed files with 28 additions and 5 deletions

View File

@@ -14,6 +14,7 @@ test("the cover drawer owns motion and gets standalone, opaque chrome", () => {
enterMotion: false,
isSinglePanelView: true,
layout: "standalone",
transcriptVariant: "conversation",
transparentChrome: false,
},
);
@@ -30,6 +31,7 @@ test("the split pane keeps docked chrome and its own enter motion", () => {
enterMotion: true,
isSinglePanelView: false,
layout: "split",
transcriptVariant: undefined,
transparentChrome: true,
},
);
@@ -47,6 +49,7 @@ test("narrow viewports keep today's overlay and single-panel presentations", ()
enterMotion: true,
isSinglePanelView,
layout: "standalone",
transcriptVariant: undefined,
transparentChrome: false,
},
);

View File

@@ -1,3 +1,5 @@
import type { AgentSessionTranscriptVariant } from "@/features/agents/ui/agentSessionTranscriptContext";
/**
* `AnimatePresence` key shared by every agent activity presentation.
*
@@ -11,6 +13,7 @@ export type AgentSessionPanelPresentation = {
enterMotion: boolean;
isSinglePanelView: boolean;
layout: "standalone" | "split";
transcriptVariant: AgentSessionTranscriptVariant | undefined;
transparentChrome: boolean;
};
@@ -24,11 +27,11 @@ type AgentSessionPanelPresentationOptions = {
/**
* 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.
* The transcript variant is pinned here rather than inferred from panel width.
* The cover drawer is the reading surface, so it gets `conversation`; every
* other host keeps the dense activity feed. Width is a proxy that breaks — the
* split pane can be dragged wide and a narrow overlay can be tall — so the
* presentation that decided to cover is what decides the reading view too.
*/
export function getAgentSessionPanelPresentation({
isCoverDrawer,
@@ -44,6 +47,7 @@ export function getAgentSessionPanelPresentation({
// it has no resizable neighbour to draw a resize border against.
isSinglePanelView: true,
layout: "standalone",
transcriptVariant: "conversation",
transparentChrome: false,
};
}
@@ -52,6 +56,10 @@ export function getAgentSessionPanelPresentation({
enterMotion: true,
isSinglePanelView: useSplitAuxiliaryPane ? false : isSinglePanelView,
layout: useSplitAuxiliaryPane ? "split" : "standalone",
// Undefined, not `"default"`: the panel already defaults, and naming it
// here would claim this function decides the non-cover variant when the
// profile panel's `compactPreview` is chosen at its own call site.
transcriptVariant: undefined,
transparentChrome: useSplitAuxiliaryPane,
};
}

View File

@@ -465,6 +465,18 @@ test.describe("agent activity cover drawer screenshots", () => {
page.getByTestId("transcript-user-message").first(),
).toBeVisible({ timeout: 10_000 });
// The reading view is pinned by presentation rather than inferred from panel
// width: the drawer is the reading surface, so its transcript renders the
// conversation variant. Asserted here rather than in
// `agent-activity-cover.spec.ts` because the marker only exists once the
// transcript has content, and this is the spec that seeds a turn. Asserted
// on the DOM marker rather than the prop so it proves the value survives the
// whole path from the presentation resolver through the panel.
await expect(panel.locator("[data-transcript-variant]")).toHaveAttribute(
"data-transcript-variant",
"conversation",
);
await waitForAnimations(page);
// Full window: the drawer against the sliver and the scrimmed channel,
// which is the part of this presentation a panel-only shot cannot show.