Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
3e9a991
docs(spec): App mode on the langchain canonical demo — itinerary cockpit
blove Jul 7, 2026
dcacffb
docs(spec): drop seed data — empty start, agent builds the plan
blove Jul 7, 2026
3c24c09
docs(plan): implementation plan — App mode on the langchain canonical…
blove Jul 7, 2026
74d45e2
feat(chat-graph): spike client-tool binding + client-tool-aware routi…
blove Jul 7, 2026
e23fb54
feat(chat-graph): add itinerary Stop state channel
blove Jul 7, 2026
cdaeeea
feat(chat-graph): planner framing + itinerary context injection when …
blove Jul 7, 2026
de8f706
feat(examples-chat): wire Google Maps key via inject-env (local only)
blove Jul 7, 2026
e55515c
feat(examples-chat): port map-bounds, geocoding, google-maps-loader
blove Jul 7, 2026
0f37460
feat(examples-chat): port ItineraryStore — empty start, value hydrati…
blove Jul 7, 2026
662c94a
feat(examples-chat): port itinerary panel/map/day-card/clear-day UI (…
blove Jul 7, 2026
cc95310
feat(examples-chat): port itinerary client tools (drop get_itinerary,…
blove Jul 7, 2026
d5bae8a
feat(examples-chat): sync itinerary — submit state + value hydration …
blove Jul 7, 2026
2ab792e
fix(examples-chat): import vitest globals in client-tools.spec (build…
blove Jul 7, 2026
62b456f
feat(examples-chat): App-mode toggle + map-compatible routing (embed↔…
blove Jul 7, 2026
7d941ad
feat(examples-chat): App-mode cockpit layout (map bg + itinerary over…
blove Jul 7, 2026
77ababf
feat(examples-chat): wire client tools + cockpit into modes; context-…
blove Jul 7, 2026
c81c7b8
fix(examples-chat): drop dangling get_itinerary reference in client-t…
blove Jul 7, 2026
6318ac3
test(examples-chat): e2e App-mode cockpit (empty state + embed coercion)
blove Jul 7, 2026
f3f29ed
fix(examples-chat): reliably persist itinerary to checkpoint (retry m…
blove Jul 7, 2026
3fb99b4
fix(examples-chat): retry checkpoint push only on 409, capped (review)
blove Jul 7, 2026
f8e1383
feat(examples-chat): dark map via colorScheme, drop cloud-style depen…
blove Jul 7, 2026
eee067b
feat(examples-chat): map light/dark follows the app color scheme
blove Jul 7, 2026
5a98f31
Merge branch 'main' into feat/langgraph-app-mode-itinerary
blove Jul 7, 2026
c7210de
Add markdown blockquote table e2e coverage
blove Jul 8, 2026
97f86b9
Merge main into app mode itinerary branch
blove Jul 9, 2026
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
8 changes: 8 additions & 0 deletions examples/chat/angular/e2e/fixtures/markdown.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,14 @@
"match": { "userMessage": "respond with a bullet list" },
"response": { "content": "Three things:\n\n- alpha\n- beta\n- gamma" }
},
{
"match": {
"userMessage": "Give me a blockquote with two lines, then a markdown table with columns issue, expected behavior, verification."
},
"response": {
"content": "> First quoted line\n> Second quoted line\n\n| Issue | Expected behavior | Verification |\n| --- | --- | --- |\n| Blockquote nesting | Both quoted lines render inside one blockquote | Inspect for one `<blockquote>` with no stray quoted paragraph |\n| Table streaming | Rows remain inside the same table while content streams | Inspect for one `<table>` with body rows |\n| Parser recovery | Final DOM has no raw pipe paragraphs | Confirm no paragraph text contains `|` delimiters |"
}
},
{
"match": { "userMessage": "respond with the markdown checklist kitchen sink" },
"response": {
Expand Down
29 changes: 29 additions & 0 deletions examples/chat/angular/e2e/markdown-surfaces.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,29 @@ test('markdown checklist matrix: rich markdown renders with escaped html', async
await expect(bubble).toContainText("<script>alert('xss')</script>");
});

test('blockquote followed by table stays in one rendered markdown surface', async ({ page }) => {
const bubble = await sendPromptAndWait(
page,
'Give me a blockquote with two lines, then a markdown table with columns issue, expected behavior, verification.',
);

await expect(bubble.locator('blockquote')).toHaveCount(1);
await expect(bubble.locator('blockquote')).toContainText('First quoted line');
await expect(bubble.locator('blockquote')).toContainText('Second quoted line');
await expect(bubble.locator('table')).toHaveCount(1);
await expect(bubble.locator('thead th')).toHaveText([
'Issue',
'Expected behavior',
'Verification',
]);
await expect(bubble.locator('tbody tr')).toHaveCount(3);
await expect.poll(async () => tableColumnsAlign(bubble)).toBe(true);
await expect(
bubble.locator('p').filter({ hasText: /\|/ }),
'table rows should not render as raw pipe paragraphs',
).toHaveCount(0);
});

test('streaming markdown table: keeps in-progress rows inside one table', async ({ page }) => {
const hygiene = attachBrowserHygiene(page);
await sendPrompt(page, 'stream a markdown comparison table regression');
Expand Down Expand Up @@ -94,13 +117,19 @@ test('streaming markdown table: blockquote followed by table does not throw', as
const bubble = await waitForFinalAssistant(page);
await expect(bubble.locator('blockquote')).toBeVisible();
await expect(bubble.locator('blockquote')).toContainText('First line of the quote.');
await expect(bubble.locator('blockquote')).toContainText('Second line of the quote.');
await expect(bubble.locator('table')).toHaveCount(1);
await expect(bubble.locator('thead th')).toHaveText([
'Issue',
'Expected behavior',
'Verification',
]);
await expect(bubble.locator('tbody tr')).toHaveCount(2);
await expect.poll(async () => tableColumnsAlign(bubble)).toBe(true);
await expect(
bubble.locator('p').filter({ hasText: /\|/ }),
'table rows should not render as raw pipe paragraphs',
).toHaveCount(0);
expect(hygiene.consoleErrors).toEqual([]);
expect(hygiene.failedRequests).toEqual([]);
});
Expand Down
8 changes: 8 additions & 0 deletions libs/chat/src/lib/styles/chat-sidenav.styles.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,14 @@ describe('CHAT_SIDENAV_STYLES — drawer elevation + z-index token', () => {
it('no longer declares the .chat-sidenav__scrim selector', () => {
expect(normalized).not.toMatch(/\.chat-sidenav__scrim\s*\{/);
});
it('keeps the closed drawer panel inert until the drawer is open', () => {
expect(normalized).toMatch(
/:host\(\[data-mode="drawer"\]\) \.chat-sidenav\s*\{[^}]*pointer-events:\s*none\s*;/,
);
expect(normalized).toMatch(
/:host\(\[data-mode="drawer"\]\[data-open="true"\]\) \.chat-sidenav\s*\{[^}]*pointer-events:\s*auto\s*;/,
);
});
});

describe('CHAT_SIDENAV_STYLES — Archived disclosure', () => {
Expand Down
9 changes: 5 additions & 4 deletions libs/chat/src/lib/styles/chat-sidenav.styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,13 +65,14 @@ export const CHAT_SIDENAV_STYLES = `
height: 100%;
transition: transform 200ms ease;
transform: translateX(-100%);
/* Re-enable pointer events on the panel itself (host is inert above). When
* closed the panel is translated off-screen so this is harmless; when open
* it covers the host box and stays fully interactive. */
pointer-events: auto;
/* Keep the closed/closing panel inert. The host is inert above, and the
* panel can still be hit during its transform transition unless pointer
* events are restored only for the open state below. */
pointer-events: none;
}
:host([data-mode="drawer"][data-open="true"]) .chat-sidenav {
transform: translateX(0);
pointer-events: auto;
}
.chat-sidenav__header {
flex-shrink: 0;
Expand Down
Loading