Skip to content

Commit 82ce3cd

Browse files
committed
feat: dogfood React Chat Interrupts in the native example
1 parent 02a7a9d commit 82ce3cd

42 files changed

Lines changed: 4592 additions & 32 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎apps/website/content/AGENTS.md.template‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ Supported Angular majors: 20, 21, and 22.
66

77
## React previews
88

9+
Chat Interrupts has an experimental native React preview at https://threadplane.ai/docs/chat/components/chat-interrupt-panel?frontend=react. It uses the existing c-interrupts graph and demo flight data with native ApprovalCard, MessageList, Markdown and ChatInput. Fixed Confirm/Cancel actions require one saved root approval and one matching pending booking; completed read tools must have causal results. The application owns the current turn, observed interrupt batch and checkpoint, and confirms saved history after submit and resume. Observed replacement or uncertain work blocks continuation until local New conversation. These page-local checks do not provide an atomic external-client checkpoint precondition. Stop detaches authority; late results cannot restore it. Opening the page and local reset create no thread. Back preserves the mounted draft; reload starts empty. Python title generation remains metadata-only. Persistent compiled-graph fixtures stay private, Angular remains the default, and React/core/content/neutral runtime candidates remain private source-build packages.
10+
911
Chat Input has an experimental native React preview at https://threadplane.ai/docs/chat/components/chat-input?frontend=react. It uses the existing c-input graph with a controlled draft, custom placeholder, keyboard preference and local disabled setting. Enter/Shift+Enter and Ctrl/Command+Enter preserve authored text; IME composition and whitespace-only drafts do not submit. Drafts stay editable during streaming and saved-history confirmation unless explicitly disabled; Stop remains available during active work. Local reset clears the old draft without erasing text typed during delayed cleanup. Back preserves the mounted draft and settings; reload restores defaults. Continuation requires exact terminal saved history, and uncertain or stopped work requires an explicit new conversation. Title generation is metadata-only. Actual compiled-graph fixtures remain private, Angular stays the default, and React/core/content/neutral runtime candidates remain private source-build packages.
1012

1113
Chat Messages has an experimental native React preview at https://threadplane.ai/docs/chat/concepts/message-model?frontend=react. It composes MessageList, Markdown and ChatInput with the existing c-messages graph. A stable owner permits continuation only after one terminal server history read preserves the confirmed prefix and exact current question/answer identities. Stop, stale reads, tools, children, interrupts and uncertain creation require an explicit new local conversation. Title generation remains metadata-only. The installed browser fixture derives events from the actual compiled graph using deterministic providers; it is excluded from public assets. The default Angular implementation and shared FREE backend remain available. React, core, content and neutral runtime candidates remain private source-build packages.

‎apps/website/content/CLAUDE.md.template‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ Supported Angular majors: 20, 21, and 22.
66

77
## React previews
88

9+
Chat Interrupts has an experimental native React preview at https://threadplane.ai/docs/chat/components/chat-interrupt-panel?frontend=react. It uses the existing c-interrupts graph and demo flight data with native ApprovalCard, MessageList, Markdown and ChatInput. Fixed Confirm/Cancel actions require one saved root approval and one matching pending booking; completed read tools must have causal results. The application owns the current turn, observed interrupt batch and checkpoint, and confirms saved history after submit and resume. Observed replacement or uncertain work blocks continuation until local New conversation. These page-local checks do not provide an atomic external-client checkpoint precondition. Stop detaches authority; late results cannot restore it. Opening the page and local reset create no thread. Back preserves the mounted draft; reload starts empty. Python title generation remains metadata-only. Persistent compiled-graph fixtures stay private, Angular remains the default, and React/core/content/neutral runtime candidates remain private source-build packages.
10+
911
Chat Input has an experimental native React preview at https://threadplane.ai/docs/chat/components/chat-input?frontend=react. It uses the existing c-input graph with a controlled draft, custom placeholder, keyboard preference and local disabled setting. Enter/Shift+Enter and Ctrl/Command+Enter preserve authored text; IME composition and whitespace-only drafts do not submit. Drafts stay editable during streaming and saved-history confirmation unless explicitly disabled; Stop remains available during active work. Local reset clears the old draft without erasing text typed during delayed cleanup. Back preserves the mounted draft and settings; reload restores defaults. Continuation requires exact terminal saved history, and uncertain or stopped work requires an explicit new conversation. Title generation is metadata-only. Actual compiled-graph fixtures remain private, Angular stays the default, and React/core/content/neutral runtime candidates remain private source-build packages.
1012

1113
Chat Messages has an experimental native React preview at https://threadplane.ai/docs/chat/concepts/message-model?frontend=react. It composes MessageList, Markdown and ChatInput with the existing c-messages graph. A stable owner permits continuation only after one terminal server history read preserves the confirmed prefix and exact current question/answer identities. Stop, stale reads, tools, children, interrupts and uncertain creation require an explicit new local conversation. Title generation remains metadata-only. The installed browser fixture derives events from the actual compiled graph using deterministic providers; it is excluded from public assets. The default Angular implementation and shared FREE backend remain available. React, core, content and neutral runtime candidates remain private source-build packages.
Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
import { test, expect, type Page } from '@playwright/test';
2+
const route = '/docs/chat/components/chat-interrupt-panel';
3+
const observations = new WeakMap<
4+
Page,
5+
{ requests: string[]; errors: string[] }
6+
>();
7+
test.beforeEach(async ({ page }) => {
8+
const observed = { requests: [] as string[], errors: [] as string[] };
9+
observations.set(page, observed);
10+
page.on('request', (request) => {
11+
if (/\/threads(?:\/|$)/.test(new URL(request.url()).pathname))
12+
observed.requests.push(request.method());
13+
});
14+
page.on('pageerror', (error) => observed.errors.push(error.message));
15+
});
16+
test.afterEach(async ({ page }) => {
17+
expect(observations.get(page)?.requests).toEqual([]);
18+
expect(observations.get(page)?.errors).toEqual([]);
19+
});
20+
async function hydrated(page: Page, mode: 'docs' | 'code' | 'run') {
21+
await expect(page.locator('[data-workspace-shell]')).toHaveAttribute(
22+
'data-hydrated',
23+
'true'
24+
);
25+
await expect(page.locator('[data-workspace-shell]')).toHaveAttribute(
26+
'data-workspace-mode',
27+
{ docs: 'Docs', code: 'Code', run: 'Run' }[mode]
28+
);
29+
}
30+
async function emptyRun(page: Page, draft = '') {
31+
await hydrated(page, 'run');
32+
await expect(page.getByLabel('Example UI')).toHaveValue('react');
33+
await expect(page).toHaveURL(/(?:\?|&)mode=run(?:&|$)/);
34+
await expect(page.locator('iframe')).toBeVisible();
35+
await expect(page.locator('iframe')).toHaveAttribute(
36+
'src',
37+
/(?:localhost:4622|chat\/interrupts\/react)/
38+
);
39+
const frame = page.frameLocator('iframe');
40+
await expect(frame.getByRole('status')).toHaveText('Ready.');
41+
await expect(
42+
frame
43+
.getByRole('region', { name: 'Conversation', exact: true })
44+
.locator('article')
45+
).toHaveCount(0);
46+
await expect(frame.getByLabel('Message', { exact: true })).toHaveValue(draft);
47+
}
48+
test('native Chat Interrupts keeps canonical Docs, Code and Run aligned', async ({
49+
page,
50+
}, testInfo) => {
51+
await page.goto(route + '?frontend=react', { waitUntil: 'domcontentloaded' });
52+
await hydrated(page, 'docs');
53+
await expect(page.getByLabel('Example UI')).toHaveValue('react');
54+
await expect(
55+
page.getByRole('heading', {
56+
name: 'React chat interrupts preview',
57+
exact: true,
58+
})
59+
).toBeVisible();
60+
await expect(
61+
page.locator(
62+
'[data-example-file="cockpit/chat/interrupts/react/src/application.ts"]'
63+
)
64+
).toBeVisible();
65+
await page.screenshot({
66+
path: testInfo.outputPath('react-chat-interrupts-docs-desktop.png'),
67+
});
68+
await page
69+
.locator('[data-workspace-desktop-navigation]')
70+
.getByRole('button', { name: 'Code', exact: true })
71+
.click();
72+
await hydrated(page, 'code');
73+
await expect(page).toHaveURL(/(?:\?|&)mode=code(?:&|$)/);
74+
await page
75+
.locator('[data-workspace-desktop-navigation]')
76+
.getByRole('button', { name: /^Run(?:,|$)/ })
77+
.click();
78+
await emptyRun(page);
79+
await page.setViewportSize({ width: 390, height: 844 });
80+
await expect(page.getByLabel('Example UI')).toBeVisible();
81+
expect(
82+
await page.evaluate(
83+
() => document.documentElement.scrollWidth <= window.innerWidth
84+
)
85+
).toBe(true);
86+
await page.screenshot({
87+
path: testInfo.outputPath('react-chat-interrupts-run-mobile.png'),
88+
});
89+
});
90+
test('the canonical message concept keeps Angular as its default frontend', async ({
91+
page,
92+
}) => {
93+
await page.goto(route, { waitUntil: 'domcontentloaded' });
94+
await hydrated(page, 'docs');
95+
await expect(page.getByLabel('Example UI')).toHaveValue('angular');
96+
await expect(
97+
page.getByRole('heading', {
98+
name: 'React chat interrupts preview',
99+
exact: true,
100+
})
101+
).toHaveCount(0);
102+
await page.getByLabel('Example UI').selectOption('react');
103+
await expect(
104+
page.getByRole('heading', {
105+
name: 'React chat interrupts preview',
106+
exact: true,
107+
})
108+
).toBeVisible();
109+
await expect(page).toHaveURL(/frontend=react/);
110+
});
111+
test('a direct React run link mounts an empty conversation without creating a thread', async ({
112+
page,
113+
}) => {
114+
await page.goto(route + '?frontend=react&mode=run', {
115+
waitUntil: 'domcontentloaded',
116+
});
117+
await emptyRun(page);
118+
});
119+
test('history preserves the mounted React owner and unsent draft without runtime requests', async ({
120+
page,
121+
}) => {
122+
await page.goto(route + '?frontend=react&mode=run', {
123+
waitUntil: 'domcontentloaded',
124+
});
125+
await emptyRun(page);
126+
const mountedFrame = await page.locator('iframe').elementHandle();
127+
await page
128+
.frameLocator('iframe')
129+
.getByLabel('Message', { exact: true })
130+
.fill('Unsent history draft');
131+
await expect(
132+
page.frameLocator('iframe').getByLabel('Message', { exact: true })
133+
).toHaveValue('Unsent history draft');
134+
const before = [...(observations.get(page)?.requests ?? [])];
135+
await page
136+
.locator('[data-workspace-desktop-navigation]')
137+
.getByRole('button', { name: 'Code', exact: true })
138+
.click();
139+
await hydrated(page, 'code');
140+
await expect(page).toHaveURL(/(?:\?|&)mode=code(?:&|$)/);
141+
await page.goBack({ waitUntil: 'domcontentloaded' });
142+
await emptyRun(page, 'Unsent history draft');
143+
expect(
144+
await page
145+
.locator('iframe')
146+
.evaluate((frame, original) => frame === original, mountedFrame)
147+
).toBe(true);
148+
expect(observations.get(page)?.requests).toEqual(before);
149+
});
150+
test('reload clears an unsent React draft without runtime requests', async ({
151+
page,
152+
}) => {
153+
await page.goto(route + '?frontend=react&mode=run', {
154+
waitUntil: 'domcontentloaded',
155+
});
156+
await emptyRun(page);
157+
const before = [...(observations.get(page)?.requests ?? [])];
158+
await page
159+
.frameLocator('iframe')
160+
.getByLabel('Message', { exact: true })
161+
.fill('Unsent reload draft');
162+
await expect(
163+
page.frameLocator('iframe').getByLabel('Message', { exact: true })
164+
).toHaveValue('Unsent reload draft');
165+
await page.reload({ waitUntil: 'domcontentloaded' });
166+
await emptyRun(page);
167+
expect(observations.get(page)?.requests).toEqual(before);
168+
});

‎apps/website/playwright.config.ts‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -388,6 +388,13 @@ export const createWebsitePlaywrightConfig = (
388388
reuseExistingServer,
389389
timeout: 180_000,
390390
},
391+
{
392+
command: 'npx nx run cockpit-chat-interrupts-python:smoke && node scripts/react-cockpit/serve.mjs chat-interrupts --no-parent',
393+
cwd: '../..',
394+
url: 'http://127.0.0.1:4622',
395+
reuseExistingServer,
396+
timeout: 180_000,
397+
},
391398
]
392399
: []),
393400
]

‎apps/website/project.json‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,8 @@
9696
"cockpit-render-element-rendering-react",
9797
"cockpit-render-computed-functions-react",
9898
"cockpit-chat-messages-react",
99-
"cockpit-chat-input-react"
99+
"cockpit-chat-input-react",
100+
"cockpit-chat-interrupts-react"
100101
]
101102
}
102103
],

‎apps/website/public/AGENTS.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ Supported Angular majors: 20, 21, and 22.
66

77
## React previews
88

9+
Chat Interrupts has an experimental native React preview at https://threadplane.ai/docs/chat/components/chat-interrupt-panel?frontend=react. It uses the existing c-interrupts graph and demo flight data with native ApprovalCard, MessageList, Markdown and ChatInput. Fixed Confirm/Cancel actions require one saved root approval and one matching pending booking; completed read tools must have causal results. The application owns the current turn, observed interrupt batch and checkpoint, and confirms saved history after submit and resume. Observed replacement or uncertain work blocks continuation until local New conversation. These page-local checks do not provide an atomic external-client checkpoint precondition. Stop detaches authority; late results cannot restore it. Opening the page and local reset create no thread. Back preserves the mounted draft; reload starts empty. Python title generation remains metadata-only. Persistent compiled-graph fixtures stay private, Angular remains the default, and React/core/content/neutral runtime candidates remain private source-build packages.
10+
911
Chat Input has an experimental native React preview at https://threadplane.ai/docs/chat/components/chat-input?frontend=react. It uses the existing c-input graph with a controlled draft, custom placeholder, keyboard preference and local disabled setting. Enter/Shift+Enter and Ctrl/Command+Enter preserve authored text; IME composition and whitespace-only drafts do not submit. Drafts stay editable during streaming and saved-history confirmation unless explicitly disabled; Stop remains available during active work. Local reset clears the old draft without erasing text typed during delayed cleanup. Back preserves the mounted draft and settings; reload restores defaults. Continuation requires exact terminal saved history, and uncertain or stopped work requires an explicit new conversation. Title generation is metadata-only. Actual compiled-graph fixtures remain private, Angular stays the default, and React/core/content/neutral runtime candidates remain private source-build packages.
1012

1113
Chat Messages has an experimental native React preview at https://threadplane.ai/docs/chat/concepts/message-model?frontend=react. It composes MessageList, Markdown and ChatInput with the existing c-messages graph. A stable owner permits continuation only after one terminal server history read preserves the confirmed prefix and exact current question/answer identities. Stop, stale reads, tools, children, interrupts and uncertain creation require an explicit new local conversation. Title generation remains metadata-only. The installed browser fixture derives events from the actual compiled graph using deterministic providers; it is excluded from public assets. The default Angular implementation and shared FREE backend remain available. React, core, content and neutral runtime candidates remain private source-build packages.

‎apps/website/public/CLAUDE.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ Supported Angular majors: 20, 21, and 22.
66

77
## React previews
88

9+
Chat Interrupts has an experimental native React preview at https://threadplane.ai/docs/chat/components/chat-interrupt-panel?frontend=react. It uses the existing c-interrupts graph and demo flight data with native ApprovalCard, MessageList, Markdown and ChatInput. Fixed Confirm/Cancel actions require one saved root approval and one matching pending booking; completed read tools must have causal results. The application owns the current turn, observed interrupt batch and checkpoint, and confirms saved history after submit and resume. Observed replacement or uncertain work blocks continuation until local New conversation. These page-local checks do not provide an atomic external-client checkpoint precondition. Stop detaches authority; late results cannot restore it. Opening the page and local reset create no thread. Back preserves the mounted draft; reload starts empty. Python title generation remains metadata-only. Persistent compiled-graph fixtures stay private, Angular remains the default, and React/core/content/neutral runtime candidates remain private source-build packages.
10+
911
Chat Input has an experimental native React preview at https://threadplane.ai/docs/chat/components/chat-input?frontend=react. It uses the existing c-input graph with a controlled draft, custom placeholder, keyboard preference and local disabled setting. Enter/Shift+Enter and Ctrl/Command+Enter preserve authored text; IME composition and whitespace-only drafts do not submit. Drafts stay editable during streaming and saved-history confirmation unless explicitly disabled; Stop remains available during active work. Local reset clears the old draft without erasing text typed during delayed cleanup. Back preserves the mounted draft and settings; reload restores defaults. Continuation requires exact terminal saved history, and uncertain or stopped work requires an explicit new conversation. Title generation is metadata-only. Actual compiled-graph fixtures remain private, Angular stays the default, and React/core/content/neutral runtime candidates remain private source-build packages.
1012

1113
Chat Messages has an experimental native React preview at https://threadplane.ai/docs/chat/concepts/message-model?frontend=react. It composes MessageList, Markdown and ChatInput with the existing c-messages graph. A stable owner permits continuation only after one terminal server history read preserves the confirmed prefix and exact current question/answer identities. Stop, stale reads, tools, children, interrupts and uncertain creation require an explicit new local conversation. Title generation remains metadata-only. The installed browser fixture derives events from the actual compiled graph using deterministic providers; it is excluded from public assets. The default Angular implementation and shared FREE backend remain available. React, core, content and neutral runtime candidates remain private source-build packages.

0 commit comments

Comments
 (0)