Skip to content

Commit bac1add

Browse files
committed
fix(ag-ui): keep app sidebar controls clickable
1 parent c91609b commit bac1add

2 files changed

Lines changed: 82 additions & 6 deletions

File tree

examples/ag-ui/angular/e2e/app-mode-promo.spec.ts

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,84 @@ test('switching between popup and sidebar preserves App mode', async ({ page })
5353
await expect(page.locator('popup-mode')).toBeVisible();
5454
});
5555

56+
test('sidebar App mode keeps the map framed when the chat rail closes and reopens', async ({ page }) => {
57+
await page.setViewportSize({ width: 1280, height: 800 });
58+
await openDemo(page, '/sidebar?appmode=on');
59+
60+
const sidebar = page.locator('chat-sidebar');
61+
const map = page.locator('sidebar-mode app-map-canvas');
62+
const panel = page.locator('.chat-sidebar__panel');
63+
64+
await expect(sidebar).toHaveAttribute('data-open', 'true');
65+
await expect(panel).toHaveAttribute('data-open', 'true');
66+
await expect(map).toBeVisible();
67+
68+
async function layoutState(): Promise<{
69+
contentWidth: number;
70+
contentRight: number;
71+
mapWidth: number;
72+
mapRight: number;
73+
marginRight: string;
74+
rootClaim: string | null;
75+
occupyRight: string;
76+
}> {
77+
return page.evaluate(() => {
78+
const contentEl = document.querySelector('.chat-sidebar__content');
79+
const mapEl = document.querySelector('sidebar-mode app-map-canvas');
80+
if (!contentEl || !mapEl) throw new Error('App-mode map layout elements missing');
81+
const contentRect = contentEl.getBoundingClientRect();
82+
const mapRect = mapEl.getBoundingClientRect();
83+
const contentStyle = getComputedStyle(contentEl);
84+
const root = document.documentElement;
85+
const rootStyle = getComputedStyle(root);
86+
return {
87+
contentWidth: contentRect.width,
88+
contentRight: contentRect.right,
89+
mapWidth: mapRect.width,
90+
mapRight: mapRect.right,
91+
marginRight: contentStyle.marginRight,
92+
rootClaim: root.getAttribute('data-threadplane-chat-sidebar'),
93+
occupyRight: rootStyle.getPropertyValue('--tplane-chat-occupy-right').trim(),
94+
};
95+
});
96+
}
97+
98+
await expect.poll(async () => parseFloat((await layoutState()).marginRight)).toBeGreaterThan(300);
99+
const openLayout = await layoutState();
100+
expect(openLayout.rootClaim).toBe('open');
101+
expect(parseFloat(openLayout.marginRight)).toBeGreaterThan(300);
102+
expect(openLayout.contentWidth).toBeGreaterThan(500);
103+
expect(openLayout.mapWidth).toBeCloseTo(openLayout.contentWidth, 0);
104+
expect(openLayout.mapRight).toBeCloseTo(openLayout.contentRight, 0);
105+
106+
await page.getByRole('button', { name: 'Close chat' }).click();
107+
await expect(sidebar).toHaveAttribute('data-open', 'false');
108+
await expect(panel).toHaveAttribute('data-open', 'false');
109+
await expect(page.getByRole('button', { name: 'Open chat' })).toBeVisible();
110+
await expect.poll(async () => parseFloat((await layoutState()).marginRight)).toBe(0);
111+
112+
const closedLayout = await layoutState();
113+
expect(closedLayout.rootClaim).toBeNull();
114+
expect(closedLayout.occupyRight).toBe('0px');
115+
expect(parseFloat(closedLayout.marginRight)).toBe(0);
116+
expect(closedLayout.contentWidth).toBeGreaterThan(openLayout.contentWidth);
117+
expect(closedLayout.mapWidth).toBeCloseTo(closedLayout.contentWidth, 0);
118+
expect(closedLayout.mapRight).toBeCloseTo(closedLayout.contentRight, 0);
119+
120+
await page.getByRole('button', { name: 'Open chat' }).click();
121+
await expect(sidebar).toHaveAttribute('data-open', 'true');
122+
await expect(panel).toHaveAttribute('data-open', 'true');
123+
await expect.poll(async () => parseFloat((await layoutState()).marginRight)).toBeGreaterThan(300);
124+
125+
const reopenedLayout = await layoutState();
126+
expect(reopenedLayout.rootClaim).toBe('open');
127+
expect(parseFloat(reopenedLayout.marginRight)).toBeGreaterThan(300);
128+
expect(reopenedLayout.contentWidth).toBeGreaterThan(500);
129+
expect(reopenedLayout.contentWidth).toBeLessThan(closedLayout.contentWidth);
130+
expect(reopenedLayout.mapWidth).toBeCloseTo(reopenedLayout.contentWidth, 0);
131+
expect(reopenedLayout.mapRight).toBeCloseTo(reopenedLayout.contentRight, 0);
132+
});
133+
56134
// Embed is full-chat with no background, so it can't host App mode: choosing
57135
// Embed from App mode turns App mode off.
58136
test('choosing embed from App mode turns App mode off and shows the full chat', async ({ page }) => {

examples/ag-ui/angular/src/app/shell/ag-ui-shell.component.css

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -181,13 +181,11 @@
181181
* viewport minus the toolbar. */
182182
height: calc(100% - var(--demo-toolbar-height));
183183
}
184-
/* chat-sidebar panel renders top-aligned with the page, NOT under the
185-
* toolbar — so the panel's close button sits at the same viewport-y as
186-
* the hamburger inside the toolbar (both at surface-top + 8 padding).
187-
* The panel's z-index is below the toolbar's so the toolbar still
188-
* renders above it where they overlap on the right edge. */
184+
/* Keep the sidebar panel below the toolbar. The toolbar has the higher z-index
185+
* for portaled select menus, so a top-aligned panel leaves its close button
186+
* visually present but intercepted by toolbar controls. */
189187
.ag-ui-shell ::ng-deep .chat-sidebar__panel {
190-
top: 0;
188+
top: var(--demo-toolbar-height);
191189
}
192190
.ag-ui-shell__theme-toggle--toolbar { margin-left: auto; }
193191

0 commit comments

Comments
 (0)