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 .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -293,7 +293,7 @@ jobs:
# `scope:shared`, which is not a SCOPE_KEY — adding it to LIBS would not
# have run it, because a workspace-react change never flips `library`.
- run: npx nx lint workspace-react
- run: npx nx run-many -t test --projects=cockpit,cockpit-docs,cockpit-registry,workspace-react --skip-nx-cache
- run: npx nx run-many -t test --projects=cockpit,cockpit-docs,cockpit-registry,cockpit-shell,workspace-react --skip-nx-cache
cockpit-examples-build:
name: Cockpit — build all examples
needs: ci-scope
Expand Down
8 changes: 0 additions & 8 deletions apps/cockpit/scripts/deploy-smoke.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,13 +128,6 @@ describe('redirect deploy smoke contract', () => {
expect(
cases.some((smokeCase) => smokeCase.name.includes('unavailable mode'))
).toBe(true);
expect(
cases.some(
(smokeCase) =>
smokeCase.name.includes('workspace Docs serialization') &&
smokeCase.expectedLocation?.includes('?mode=docs')
)
).toBe(true);
expect(
cases.some(
(smokeCase) =>
Expand Down Expand Up @@ -201,7 +194,6 @@ describe('redirect deploy smoke contract', () => {
for (const label of [
'root',
'Docs-backed',
'workspace-only',
'unknown',
'favicon',
'raw malformed',
Expand Down
32 changes: 3 additions & 29 deletions apps/cockpit/scripts/deploy-smoke.ts
Original file line number Diff line number Diff line change
Expand Up @@ -236,20 +236,6 @@ const buildPreviewCases = (): RedirectSmokeCase[] => {
);
}

const workspaceOnly = cockpitManifest.find((entry) =>
getWorkspaceDestinationPath(entry).startsWith('/workspace/')
);
if (!workspaceOnly)
throw new Error('Expected a workspace-only manifest entry');
cases.push(
redirectCase(
'workspace Docs serialization',
`${workspaceOnly.legacyPath}?mode=docs`,
entryResolution(workspaceOnly),
'docs'
)
);

cases.push(
notFoundCase('unknown path 404', '/unknown'),
notFoundCase('partial path 404', '/langgraph/core-capabilities/streaming'),
Expand Down Expand Up @@ -285,13 +271,8 @@ const buildProductionCases = (): RedirectSmokeCase[] => {
const docsBacked = cockpitManifest.find((entry) =>
getWorkspaceDestinationPath(entry).startsWith('/docs/')
);
const workspaceOnly = cockpitManifest.find((entry) =>
getWorkspaceDestinationPath(entry).startsWith('/workspace/')
);
if (!docsBacked || !workspaceOnly) {
throw new Error(
'Redirect smoke requires Docs-backed and workspace-only routes'
);
if (!docsBacked) {
throw new Error('Redirect smoke requires a Docs-backed route');
}
return [
redirectCase('root production redirect', '/', root, 'run'),
Expand All @@ -300,11 +281,6 @@ const buildProductionCases = (): RedirectSmokeCase[] => {
docsBacked.legacyPath,
entryResolution(docsBacked)
),
redirectCase(
'workspace-only production redirect',
workspaceOnly.legacyPath,
entryResolution(workspaceOnly)
),
notFoundCase('unknown production 404', '/unknown'),
{
name: 'favicon production redirect',
Expand Down Expand Up @@ -442,9 +418,7 @@ const verifyCase = (
if (response.status !== smokeCase.expectedStatus) {
const protectionHint =
response.status === 302 &&
(response.headers.location ?? '').startsWith(
'https://vercel.com/sso-api'
)
(response.headers.location ?? '').startsWith('https://vercel.com/sso-api')
? ` The deployment answered with Vercel deployment protection, not the redirect service; supply the owning project's automation bypass secret via ${BYPASS_SECRET_ENV}.`
: '';
throw new RedirectContractError(
Expand Down
17 changes: 3 additions & 14 deletions apps/cockpit/src/lib/cockpit-page.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -266,15 +266,12 @@ describe('registry-derived legacy Website redirects', () => {
).toBe(expectedHref(docsOnly, 'Docs'));
});

it('serializes Docs mode truthfully for docs and workspace destinations', () => {
it('serializes Docs mode truthfully for docs destinations', () => {
const docsDestination = cockpitManifest.find((entry) =>
getWorkspaceDestinationPath(entry).startsWith('/docs/')
);
const workspaceDestination = cockpitManifest.find((entry) =>
getWorkspaceDestinationPath(entry).startsWith('/workspace/')
);
if (!docsDestination || !workspaceDestination) {
throw new Error('Expected docs and workspace fixtures');
if (!docsDestination) {
throw new Error('Expected a docs fixture');
}

expect(
Expand All @@ -284,14 +281,6 @@ describe('registry-derived legacy Website redirects', () => {
productionEnvironment
)
).toBe(expectedHref(docsDestination, 'Docs'));
expect(
getLegacyWebsiteRedirect(
workspaceDestination.legacyPath,
['docs'],
productionEnvironment
)
).toBe(expectedHref(workspaceDestination, 'Docs'));
expect(expectedHref(workspaceDestination, 'Docs')).toContain('?mode=docs');
});

it('returns null for unknown, partial, extra, malformed, and trailing paths', () => {
Expand Down
9 changes: 9 additions & 0 deletions apps/website/content/docs/ag-ui/guides/client-tools.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
description: Declare a tool in the Angular app, let an AG-UI agent call it, and submit the result back to continue the run with no server-side implementation.
---

# Client Tools

An AG-UI agent can call tools that live in the browser. The Angular app declares the tool, the backend ends its turn when the agent calls it, the browser runs it, and the result is submitted back as a tool message so the run continues. The tool has no server-side implementation.

This page is the live example for client tools over AG-UI. Use **Run** to drive the agent, **Code** to read the Angular and Python sources, and **API** for the extracted reference. The browser-side tool contract is documented in the [Chat client tools guide](/docs/chat/guides/client-tools).
9 changes: 9 additions & 0 deletions apps/website/content/docs/ag-ui/guides/json-render.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
description: Stream a json-render UI spec with AG-UI shared state and let @threadplane/render resolve $state bindings into a live Angular component tree.
---

# Generative UI

An AG-UI agent can stream a declarative json-render specification together with shared state. `STATE_SNAPSHOT` and `STATE_DELTA` events keep that state current, and `@threadplane/render` resolves the spec's `$state` bindings against it, so the agent shapes the interface and its data rather than only its text.

This page is the live example for generative UI over AG-UI. Use **Run** to drive the agent, **Code** to read the Angular and Python sources, and **API** for the extracted reference. The rendering engine is introduced in the [Render introduction](/docs/render/getting-started/introduction), and the AG-UI state events in the [event mapping reference](/docs/ag-ui/reference/event-mapping).
9 changes: 9 additions & 0 deletions apps/website/content/docs/ag-ui/guides/subagents.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
description: Show an AG-UI agent's delegated child runs as attributed subagent cards with their own tool calls and messages.
---

# Subagents

AG-UI carries subagent activity as first-class events. When an agent delegates, each child run appears as an attributed card with its own tool calls and messages instead of being folded into the parent transcript.

This page is the live example for subagents over AG-UI. Use **Run** to drive the agent, **Code** to read the Angular and Python sources, and **API** for the extracted reference. The card component is documented in [ChatSubagentCard](/docs/chat/components/chat-subagent-card).
9 changes: 9 additions & 0 deletions apps/website/content/docs/ag-ui/guides/tool-views.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
description: Render an AG-UI tool call's plain data through a component the frontend owns, keyed by tool name, with no UI spec crossing the wire.
---

# Tool Views

A tool view renders an AG-UI tool call's result through a component the frontend owns, keyed by the tool's name. The agent returns plain data, such as a weather reading, and the Angular app decides how it looks. No UI specification crosses the wire.

This page is the live example for tool views over AG-UI. Use **Run** to drive the agent, **Code** to read the Angular and Python sources, and **API** for the extracted reference. The tool-call component surface is documented in [ChatToolCalls](/docs/chat/components/chat-tool-calls).
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
description: Resume a LangGraph run after a crash, restart, or redeploy from its last checkpoint, and offer retry from the Angular UI.
---

# Durable Execution

Persistence keeps a thread's history; durable execution keeps a _run_ alive through failure. When a step crashes, the process restarts, or a deployment rolls, the checkpointer lets the run resume from its last completed super-step instead of starting over, and the UI can offer a retry rather than a blank transcript.

This page is the live example for durable execution over LangGraph. Use **Run** to interrupt and retry a run, **Code** to read the Angular and Python sources, and **API** for the extracted reference. The checkpointer configuration it relies on is written up in the [Persistence guide](/docs/langgraph/guides/persistence).
9 changes: 9 additions & 0 deletions apps/website/content/docs/render/guides/repeat-loops.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
description: Render one element per item of a bound collection with $item and $index in a json-render spec, backed by a signal state store.
---

# Repeat Loops

A repeat loop renders one element per item of a bound collection, with `$item` and `$index` available to child expressions, so a spec can describe a list without enumerating its rows. The example binds the loop to a signal-backed state store and updates the list live.

This page is the live example for repeat loops. Use **Run** to try the example live (it has no agent), **Code** to read the Angular source, and **API** for the extracted reference. The spec format, including the repeat element, is documented in [Specs & Elements](/docs/render/guides/specs).
29 changes: 9 additions & 20 deletions apps/website/e2e/platform-production-smoke.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,10 @@ const expectedRuntimeParentOrigins = validateRuntimeParentOrigins([
...(baseRuntimeParentOrigins ?? []),
...runtimeParentPreviewOrigins,
]);
if (baseRuntimeParentOrigins === null || expectedRuntimeParentOrigins === null) {
if (
baseRuntimeParentOrigins === null ||
expectedRuntimeParentOrigins === null
) {
throw new Error('Invalid runtime parent origin smoke policy');
}

Expand Down Expand Up @@ -140,12 +143,8 @@ const expectedRedirect = (legacyPath: string): string => {
const docsBacked = cockpitManifest.find((entry) =>
getWorkspaceDestinationPath(entry).startsWith('/docs/')
);
const workspaceOnly = cockpitManifest.find((entry) =>
getWorkspaceDestinationPath(entry).startsWith('/workspace/')
);
if (!docsBacked || !workspaceOnly) {
throw new Error('Production smoke requires Docs-backed and workspace routes');
}
if (!docsBacked)
throw new Error('Production smoke requires a Docs-backed route');

const COCKPIT_REDIRECT_CASES = [
{
Expand All @@ -162,12 +161,6 @@ const COCKPIT_REDIRECT_CASES = [
status: 308,
location: expectedRedirect(docsBacked.legacyPath),
},
{
name: 'workspace-only production redirect',
path: workspaceOnly.legacyPath,
status: 308,
location: expectedRedirect(workspaceOnly.legacyPath),
},
{
name: 'unknown production 404',
path: '/unknown',
Expand Down Expand Up @@ -259,13 +252,9 @@ test.describe('Production: unified runtime embedding policy', () => {
.find((directive) => directive.trim().startsWith('frame-ancestors'));

expect(response.status()).toBe(200);
const actualFrameAncestors = frameAncestors
?.trim()
.split(/\s+/)
.slice(1);
const validatedFrameAncestors = validateRuntimeParentOrigins(
actualFrameAncestors
);
const actualFrameAncestors = frameAncestors?.trim().split(/\s+/).slice(1);
const validatedFrameAncestors =
validateRuntimeParentOrigins(actualFrameAncestors);
expect(validatedFrameAncestors).not.toBeNull();
if (runtimeParentPreviewOrigins.length > 0) {
expect(validatedFrameAncestors).toEqual(expectedRuntimeParentOrigins);
Expand Down
24 changes: 10 additions & 14 deletions apps/website/e2e/workspace-shell.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@ const streamingDocsPath = '/docs/langgraph/guides/streaming';
const persistenceDocsPath = '/docs/langgraph/guides/persistence';
const mappedDocsOnlyPath = '/docs/langgraph/getting-started/introduction';
const unmappedDocsOnlyPath = '/docs/langgraph/getting-started/installation';
const workspaceOnlyPath = '/workspace/langgraph/durable-execution';
const deepAgentsDocsPath = '/docs/deep-agents/capabilities/planning';
const durableExecutionDocsPath = '/docs/langgraph/guides/durable-execution';
const RUN_RAIL_ITEM = /^Run(?:,|$)/;

declare global {
Expand Down Expand Up @@ -409,28 +408,25 @@ test.describe('workspace shell', () => {
await expect(page.locator('[data-workspace-shell]')).toBeVisible();
});

test('uses workspace fallbacks only when a shared Docs path would lose identity', async ({
test('serves the formerly workspace-only capabilities as docs pages with Run available', async ({
page,
}) => {
const response = await page.goto(workspaceOnlyPath);
const response = await page.goto(durableExecutionDocsPath);
expect(response?.status()).toBe(200);
await expect(page).toHaveURL(workspaceOnlyPath);
await expect(page.locator('[data-workspace-shell]')).toHaveAttribute(
'aria-label',
'Website workspace'
);
await expectMode(page, 'Run');

await page.goto(deepAgentsDocsPath);
await expect(page).toHaveURL(deepAgentsDocsPath);
await expect(page).toHaveURL(durableExecutionDocsPath);
await expect(page.locator('[data-workspace-shell]')).toHaveAttribute(
'aria-label',
'Documentation workspace'
);
await expectMode(page, 'Docs');
await modeButton(page, 'Run').click();
await expect(page).toHaveURL(`${durableExecutionDocsPath}?mode=run`);
await expect(
page.locator('iframe[title="Deep Agents Planning live example"]')
page.locator('iframe[title="LangGraph Durable Execution live example"]')
).toBeAttached();

const missing = await page.goto('/workspace/langgraph/durable-execution');
expect(missing?.status()).toBe(404);
});

test('renders the full desktop rail and context at the 64rem breakpoint', async ({
Expand Down
20 changes: 1 addition & 19 deletions apps/website/src/app/docs/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,7 @@ import { DocsControlPlane } from '../../components/docs/DocsControlPlane';
import { DocsSearch } from '../../components/docs/DocsSearch';
import { DocsSearchFooter } from '../../components/docs/DocsSearchFooter';
import { createPageMetadata } from '../../lib/site-metadata';
import {
getCanonicalWebsiteWorkspaceHref,
resolveWorkspacePath,
} from '@threadplane/cockpit-registry';
import { DEFAULT_EXAMPLE_RUN_HREF } from '../../lib/docs-index-example';

export const metadata = createPageMetadata({
title: 'Documentation — Threadplane',
Expand All @@ -22,21 +19,6 @@ export const metadata = createPageMetadata({
type: 'website',
});

/**
* The example the index's Run rail item opens.
*
* Resolved through the registry rather than written as a path: this
* capability publishes a `docsPath`, so `getWorkspaceDestinationPath()` makes
* its canonical destination the docs route and `/workspace/langgraph/streaming`
* 404s. Today this yields `/docs/langgraph/guides/streaming?mode=run`, and it
* stays correct if that docs path moves. A renamed or removed capability
* resolves to null, and Run falls back to disabled rather than to a dead link.
*/
const DEFAULT_EXAMPLE_RESOLUTION = resolveWorkspacePath('/workspace/langgraph/streaming');
const DEFAULT_EXAMPLE_RUN_HREF = DEFAULT_EXAMPLE_RESOLUTION
? getCanonicalWebsiteWorkspaceHref(DEFAULT_EXAMPLE_RESOLUTION, 'Run')
: undefined;

interface Backend {
title: string;
blurb: string;
Expand Down
Loading
Loading