fix(ui-trajectory): own composer overlay geometry

This commit is contained in:
_Kerman
2026-08-03 14:02:40 +08:00
parent 062a1507f1
commit 5534431d42
6 changed files with 47 additions and 31 deletions

View File

@@ -138,6 +138,23 @@ describe('web e2e: navigation & panes over a rich seeded session', () => {
onTestFailed(() => saveFailureShot(page, 'web-e2e-navigation-trajectory'))
await page.getByRole('tab', { name: 'Trajectory' }).click()
await page.waitForTimeout(100)
const overlayLayout = await page.getByRole('table').evaluate((table) => {
const host = table.closest('[data-conversation-scroll]')
const seat = host?.querySelector('[data-composer-seat]') ?? null
const pane = table.parentElement
return {
hostPosition: host === null ? null : getComputedStyle(host).position,
paneOverflowX: pane === null ? null : getComputedStyle(pane).overflowX,
paneScrollableWidth: pane === null ? null : pane.scrollWidth - pane.clientWidth,
seatPosition: seat === null ? null : getComputedStyle(seat).position,
}
})
expect(overlayLayout).toEqual({
hostPosition: 'relative',
paneOverflowX: 'hidden',
paneScrollableWidth: 0,
seatPosition: 'absolute',
})
expect({
pageErrors: tripwire.pageErrors,
slotErrors,
@@ -153,6 +170,8 @@ describe('web e2e: navigation & panes over a rich seeded session', () => {
await page.locator('tr[data-kind="tool"]').first().click()
const details = page.getByRole('complementary', { name: 'Event details' })
await expect.poll(() => details.count(), { timeout: 10_000 }).toBe(1)
expect(await details.getByRole('tabpanel').evaluate(panel => getComputedStyle(panel).overflowX))
.toBe('hidden')
await page.evaluate(() => { document.body.setAttribute('data-ds-dark-theme', '') })
const darkSummarySurfaces = await details.getByRole('heading', { name: 'Payload' }).evaluate(heading => ({
heading: getComputedStyle(heading).backgroundColor,

View File

@@ -208,6 +208,26 @@
);
}
/* Views may opt into a composer overlay while ConversationRoot retains
ownership of the seat geometry and its active-phase precedence. */
.scrollBody:has([data-conversation-composer-overlay]) {
position: relative;
overflow: hidden;
}
.scrollBody:has([data-conversation-composer-overlay]) > .viewArea {
flex: 1 1 0;
min-height: 0;
overflow: hidden;
}
.scrollBody:has([data-conversation-composer-overlay]) > .composerSeat {
position: absolute;
right: 0;
bottom: 0;
left: 0;
}
/* Hero phase: the composer stack (hero chrome + workspace row + card) is
flex-centered in the column; composer phase docks it at the bottom. Flex,
NOT absolute+transform: a transform would make this box the containing

View File

@@ -15,7 +15,8 @@
.tablePane {
flex: 1;
min-width: 0;
overflow: auto;
overflow-x: hidden;
overflow-y: auto;
padding-bottom: var(--dsh-trajectory-bottom-clearance, 0px);
container: trajectory-table / inline-size;
}
@@ -28,7 +29,7 @@
);
width: 100%;
min-width: 480px;
min-width: 0;
border-spacing: 0;
table-layout: fixed;
color: var(--dsw-alias-label-primary);
@@ -905,7 +906,8 @@
.detailBody {
flex: 1;
min-height: 0;
overflow: auto;
overflow-x: hidden;
overflow-y: auto;
padding-bottom: var(--dsh-trajectory-bottom-clearance, 0px);
scrollbar-gutter: stable;
}

View File

@@ -459,7 +459,7 @@ export function TrajectoryView({
}
return (
<div className={css.root}>
<div className={css.root} data-conversation-composer-overlay="">
<TrajectoryToolbar
actualDuration={actualDuration}
onActualDurationChange={(nextActualDuration) => {

View File

@@ -13,30 +13,6 @@
background: var(--dsw-alias-bg-layer-1);
}
/* Trajectory keeps the ledger and details panel inside the remaining
* conversation height; only the internal panes scroll. The composer floats
* over the ledger like chat's sticky seat — absolute, not sticky, because
* this host does not scroll. */
:global([data-conversation-scroll]):has(.root) {
position: relative;
overflow: hidden;
}
:global([data-conversation-scroll]):has(.root) > :first-child {
flex: 1 1 0;
min-height: 0;
overflow: hidden;
}
/* div qualifier outranks ConversationRoot's active-phase sticky rule (equal
* specificity otherwise, and cross-module source order is bundler-defined). */
:global([data-conversation-scroll]):has(.root) > :global(div[data-composer-seat]) {
position: absolute;
right: 0;
bottom: 0;
left: 0;
}
.ledger {
position: relative;
z-index: 0;
@@ -47,8 +23,6 @@
min-width: 0;
overflow: hidden;
/* Internal panes reserve the floating composer's live height plus a 16px
* breathing gap so end rows and detail bodies can scroll clear of the
* overlay. */
/* ConversationRoot publishes the floating composer's live height. */
--dsh-trajectory-bottom-clearance: calc(var(--dsh-composer-height, 152px) + 16px);
}

View File

@@ -288,6 +288,7 @@ describe('tab switching in ConversationRoot', () => {
expect(screen.queryByRole('columnheader')).toBeNull()
expect(screen.getByRole('toolbar', { name: 'Trajectory toolbar' })).toBeTruthy()
expect(screen.getByRole('region', { name: 'Trajectory timeline' })).toBeTruthy()
expect(view.container.querySelector('[data-conversation-composer-overlay]')).toBeTruthy()
fireEvent.click(screen.getByRole('button', { name: 'Collapse turns' }))
expect(view.container.querySelector('[data-collapsed-summary="turn"]')).toBeTruthy()
fireEvent.click(screen.getByRole('button', { name: 'Expand turns' }))