Skip to content
Merged
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
2 changes: 1 addition & 1 deletion examples/chat/angular/e2e/control-palette.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ test('control palette: toolbar renders defaults and persists selected controls',
// chat-select trigger displays the option's label as text, not the value.
await expect(toolbarSelect(page, 'Model')).toHaveText(/gpt-5-mini/);
await expect(toolbarSelect(page, 'Effort')).toHaveText(/minimal \(fast\)/);
await expect(toolbarSelect(page, 'Gen UI')).toHaveText(/A2UI v1-compatible/);
await expect(toolbarSelect(page, 'Gen UI')).toHaveText(/A2UI/);
await expect(toolbarSelect(page, 'Theme')).toHaveText(/Default dark/);

await selectToolbarOption(page, 'Model', 'gpt-5-nano');
Expand Down
25 changes: 10 additions & 15 deletions examples/chat/angular/e2e/debug-devtools.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,16 @@ test('chat-debug devtools: opens from the sidenav with accessible controls and c
});

test.describe('chat-debug × chat-sidebar coexistence', () => {
test('sidebar launcher remains reachable while chat-debug is open', async ({
test('sidebar surface remains reachable while chat-debug is open', async ({
page,
}) => {
await openDemo(page, '/sidebar');
await expect(page.locator('chat-sidebar')).toBeAttached();

// Open chat-debug from the sidenav footer.
// Sidebar mode auto-opens its panel on entry. With the panel open the
// launcher is hidden by design (its close button on the panel handles
// dismissal). Verify the open chat-sidebar surface stays reachable
// (its close button is visible) while chat-debug is open.
await openChatDevtools(page);

// Debug auto-picks bottom dock because <chat-sidebar> is present.
Expand All @@ -40,22 +43,14 @@ test.describe('chat-debug × chat-sidebar coexistence', () => {
'bottom'
);

// Sidebar launcher remains visible (the bottom dock did not cover it).
// Click the actual <button> inside <chat-launcher-button> rather than the
// wrapping div — avoids any hit-test ambiguity between the wrapper and
// the higher-z-index debug panel.
const sidebarLauncherButton = page.locator(
'.chat-sidebar__launcher button.chat-launcher-button'
);
await expect(sidebarLauncherButton).toBeVisible();
await sidebarLauncherButton.click();

// Sidebar panel slides in — the click was not intercepted by the debug
// panel, which is the user-visible bug this design fixes.
// The chat-sidebar panel is auto-opened on entry — verify it remains
// visible (the bottom-docked debug panel did not cover or unmount it)
// and the close button stays reachable.
const sidebarPanel = page.locator('.chat-sidebar__panel[data-open="true"]');
await expect(sidebarPanel).toBeVisible();
await expect(sidebarPanel.locator('.chat-sidebar__close')).toBeVisible();

// Once the sidebar is open, the edge-claim attribute reflects it too.
// The edge-claim attribute reflects the open sidebar.
await expect(page.locator('html')).toHaveAttribute(
'data-ngaf-chat-sidebar',
'open'
Expand Down
4 changes: 3 additions & 1 deletion examples/chat/angular/e2e/keyboard-accessibility.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,9 @@ test('keyboard: Escape closes popup and sidebar panels', async ({ page }) => {

await page.goto('/sidebar');
await closeChatDevtools(page);
await page.locator('.chat-sidebar__launcher button.chat-launcher-button').click();
// Sidebar mode auto-opens the panel on entry (the launcher would be
// hidden in that state). Verify the panel is already open, then
// Escape closes it.
await expect(page.getByRole('complementary')).toHaveAttribute('aria-hidden', 'false');
await page.keyboard.press('Escape');
await expect(page.locator('.chat-sidebar__panel')).toHaveAttribute('aria-hidden', 'true');
Expand Down
25 changes: 18 additions & 7 deletions examples/chat/angular/e2e/lifecycle.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,27 +24,38 @@ test('lifecycle: reload reconnects to the active conversation', async ({
).toContainText(/hi/i);
});

test('lifecycle: new conversation clears local thread and restores welcome state', async ({
test('lifecycle: New chat (sidenav) starts a fresh thread and restores welcome state', async ({
page,
}) => {
await openDemo(page, '/embed');
await messageInput(page).fill('say hi briefly');
await sendButton(page).click();
await waitForFinalAssistant(page);

await page.getByRole('button', { name: 'New conversation' }).click();
const threadIdBefore = await page.evaluate(() => {
const raw = localStorage.getItem('ngaf-chat-demo:palette');
return raw ? (JSON.parse(raw) as { threadId?: string | null }).threadId ?? null : null;
});

// The toolbar "New conversation" button was removed; the sidenav's
// "New chat" pill is now the only affordance for starting a fresh
// thread. It creates a new thread server-side (rather than clearing
// local state) and routes the UI back to the welcome surface.
await page.getByRole('button', { name: 'New chat' }).first().click();

await expect(
page.getByRole('heading', { name: 'How can I help?' })
).toBeVisible();
await expect(page.locator('chat-message')).toHaveCount(0);
const threadId = await page.evaluate(() => {

const threadIdAfter = await page.evaluate(() => {
const raw = localStorage.getItem('ngaf-chat-demo:palette');
return raw
? (JSON.parse(raw) as { threadId?: string | null }).threadId
: undefined;
return raw ? (JSON.parse(raw) as { threadId?: string | null }).threadId ?? null : null;
});
expect(threadId ?? null).toBeNull();
// A fresh thread id was persisted, and it's different from the one we
// had before clicking New chat.
expect(threadIdAfter).toBeTruthy();
expect(threadIdAfter).not.toBe(threadIdBefore);
});

test('lifecycle: selecting a welcome suggestion submits and clears welcome state', async ({
Expand Down
9 changes: 6 additions & 3 deletions examples/chat/angular/e2e/mode-routing.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,13 @@ test('mode routing: embed, popup, and sidebar expose the expected landmarks', as
await page.goto('/sidebar');
await expect(page.locator('sidebar-mode chat-sidebar')).toBeVisible();
await closeChatDevtools(page);
await page.locator('.chat-sidebar__launcher button.chat-launcher-button').click();
// Sidebar mode auto-opens the panel on entry — no launcher click needed.
const sidebar = page.getByRole('complementary');
await expect(sidebar).toBeVisible();
await expect(sidebar).toHaveAttribute('aria-hidden', 'false');
await page.locator('.chat-sidebar__close').click();
// Escape dismisses the panel (closeOnEscape default true on chat-sidebar).
// Avoids click-action races against any overlapping debug/sidenav chrome.
await page.keyboard.press('Escape');
await expect(page).toHaveURL(/\/sidebar$/);
await expect(page.locator('.chat-sidebar__panel')).toHaveAttribute('aria-hidden', 'true');
});
Expand All @@ -51,7 +53,8 @@ test('cross-mode persistence: conversation follows embed, popup, and sidebar', a

await page.goto('/sidebar');
await closeChatDevtools(page);
await page.locator('.chat-sidebar__launcher button.chat-launcher-button').click();
// Sidebar mode auto-opens the panel; assert the existing conversation
// is visible without a launcher click.
await expect(
page.getByRole('complementary').locator('chat-message[data-role="assistant"]'),
).toContainText(/hi/i, { timeout: 30_000 });
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import { WelcomeSuggestionsComponent } from './welcome-suggestions.component';
</chat>
`,
styles: [`
:host { display: block; height: 100%; }
:host { display: block; flex: 1; min-height: 0; }
`],
})
export class EmbedMode {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ import { WelcomeSuggestionsComponent } from './welcome-suggestions.component';
</chat-popup>
`,
styles: [`
:host { display: block; height: 100%; }
:host { display: block; flex: 1; min-height: 0; }
.popup-mode__background {
display: grid;
place-items: center;
Expand Down
5 changes: 3 additions & 2 deletions examples/chat/angular/src/app/modes/sidebar-mode.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,15 @@ import { WelcomeSuggestionsComponent } from './welcome-suggestions.component';
template: `
<div class="sidebar-mode__background">
<p class="sidebar-mode__hint">
Click the launcher button (right edge) to slide in the chat panel.
Use the launcher (right edge) to dismiss or re-open the chat panel.
</p>
</div>
<chat-sidebar
[agent]="agent"
[views]="catalog"
[modelOptions]="shell.modelOptions()"
[selectedModel]="shell.model()"
[open]="true"
(selectedModelChange)="shell.onModelChange($event)"
(replayRequested)="shell.onTimelineReplay($event)"
(forkRequested)="shell.onTimelineFork($event)"
Expand All @@ -29,7 +30,7 @@ import { WelcomeSuggestionsComponent } from './welcome-suggestions.component';
</chat-sidebar>
`,
styles: [`
:host { display: block; height: 100%; }
:host { display: block; flex: 1; min-height: 0; }
.sidebar-mode__background {
display: grid;
place-items: center;
Expand Down
7 changes: 4 additions & 3 deletions examples/chat/angular/src/app/shell/demo-shell.component.css
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@
min-height: 48px;
display: flex;
align-items: center;
flex-wrap: wrap;
row-gap: 6px;
gap: 10px;
padding: 8px 14px;
border-bottom: 1px solid var(--ngaf-chat-separator);
Expand All @@ -69,9 +71,8 @@
* chat content's scroll/transform stacking contexts below. */
position: relative;
z-index: 50;
/* overflow-x: auto would clip absolutely-positioned dropdown menus.
* Skip it — the toolbar fits on reasonable viewports and the per-field
* dropdowns hold the long labels via the chat-select popover. */
/* No overflow-x: that would clip absolutely-positioned dropdown menus.
* flex-wrap handles narrow viewports by wrapping fields to a new row. */
}

/* Push every field after Mode to the right of the toolbar. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,7 @@ export class DemoShell {
]);

protected readonly genUiOptions = signal<readonly { value: string; label: string }[]>([
{ value: 'a2ui', label: 'A2UI v1-compatible' },
{ value: 'a2ui', label: 'A2UI' },
{ value: 'json-render', label: 'json-render' },
]);

Expand Down