Skip to content

Commit 0cffb70

Browse files
bloveclaude
andauthored
feat: one route family — every capability is a docs page (#1010)
* docs(specs): docs and workspace unification design Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * docs(plans): docs and workspace unification implementation plan Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * feat(registry): every capability publishes a unique docs path Six capabilities used to share a docs page with a sibling (durable-execution/persistence, ag-ui tool-views/json-render/ client-tools/subagents, render repeat-loops/specs) and relied on PRIMARY_CAPABILITY_BY_DOCS_PATH to pick a primary owner for the route. Give each its own docs path instead, so the override table and its validate-manifest ambiguity check can go away. The six new docs paths (e.g. /docs/langgraph/guides/ durable-execution) do not have pages yet - that lands in a follow-up task alongside removing the /workspace route. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * test(cockpit-shell): expect the moved capabilities' own docs paths The durable-execution capability fixture in workspace-presentation.spec.ts still pinned the old shared docs path (/docs/langgraph/guides/persistence) that the registry no longer publishes for it. Update the expectation to /docs/langgraph/guides/durable-execution, its own path. No other cockpit-shell/workspace-react fixture pins one of the other five moved capabilities' old shared paths (the one remaining hit on /docs/render/guides/specs belongs to spec-rendering, the primary sibling, and is unaffected). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fix(examples): mirror the moved capabilities' docs paths in the Angular descriptors The Python siblings for the six capabilities that got their own docs path (durable-execution, ag-ui tool-views/json-render/client-tools/ subagents, render repeat-loops) were updated, but each Angular mirror in cockpit/*/*/angular/src/index.ts still carried the old shared path. The Website drift guard (apps/website/src/lib/cockpit-docs-links.spec.ts) scans every cockpit/**/src/index.ts, so the Angular copies needed the same update. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * chore(deployments): regenerate ag-ui-dev artifacts after the docs path move deployments/ag-ui-dev/deps/** is a generated mirror of the cockpit/ag-ui/*/python sources. The docsPath moves for tool-views, json-render, client-tools, and subagents (plus the earlier prettier reflow of two of those sources) left the generated copies stale, which .github/workflows/deploy-ag-ui.yml would have caught post-merge via `git diff --exit-code -- deployments/ag-ui-dev/`. Regenerated with scripts/generate-ag-ui-deployment-config.ts. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * docs(plans): A3 also retires the workspace-only cases in the deploy smoke and wires cockpit-shell into CI Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * docs: give the six workspace-only capabilities their own guides Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * docs: correct the six new guides — accurate framing, descriptions, generative UI title Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * feat(website): retire the /workspace route; every capability is a docs page Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fix(website): keep the docs index Run link honest after the resolver swap; drop dead workspace helpers - resolveDocsWorkspace never returns null (a miss resolves docs-only), so the docs index's Run href guard is now gated on resolution.kind === 'mapped' instead of a dead null check, moved into a lib module with a unit test that would have caught the regression - drop dead ?? fallbacks on the now-nonnullable WorkspaceIdentity.docsPath in workspace-presentation.ts - delete the unused getWebsiteWorkspaceHref helper - fix WebsiteWorkspace.spec.tsx tests that override docsSlot: undefined even though the docs route they model always supplies a slot Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * docs(plans): Part C also removes resolveWorkspacePath and workspacePath; docsPath is non-nullable Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
1 parent 609d334 commit 0cffb70

50 files changed

Lines changed: 1971 additions & 676 deletions

File tree

Some content is hidden

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

‎.github/workflows/ci.yml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -293,7 +293,7 @@ jobs:
293293
# `scope:shared`, which is not a SCOPE_KEY — adding it to LIBS would not
294294
# have run it, because a workspace-react change never flips `library`.
295295
- run: npx nx lint workspace-react
296-
- run: npx nx run-many -t test --projects=cockpit,cockpit-docs,cockpit-registry,workspace-react --skip-nx-cache
296+
- run: npx nx run-many -t test --projects=cockpit,cockpit-docs,cockpit-registry,cockpit-shell,workspace-react --skip-nx-cache
297297
cockpit-examples-build:
298298
name: Cockpit — build all examples
299299
needs: ci-scope

‎apps/cockpit/scripts/deploy-smoke.spec.ts‎

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -128,13 +128,6 @@ describe('redirect deploy smoke contract', () => {
128128
expect(
129129
cases.some((smokeCase) => smokeCase.name.includes('unavailable mode'))
130130
).toBe(true);
131-
expect(
132-
cases.some(
133-
(smokeCase) =>
134-
smokeCase.name.includes('workspace Docs serialization') &&
135-
smokeCase.expectedLocation?.includes('?mode=docs')
136-
)
137-
).toBe(true);
138131
expect(
139132
cases.some(
140133
(smokeCase) =>
@@ -201,7 +194,6 @@ describe('redirect deploy smoke contract', () => {
201194
for (const label of [
202195
'root',
203196
'Docs-backed',
204-
'workspace-only',
205197
'unknown',
206198
'favicon',
207199
'raw malformed',

‎apps/cockpit/scripts/deploy-smoke.ts‎

Lines changed: 3 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -236,20 +236,6 @@ const buildPreviewCases = (): RedirectSmokeCase[] => {
236236
);
237237
}
238238

239-
const workspaceOnly = cockpitManifest.find((entry) =>
240-
getWorkspaceDestinationPath(entry).startsWith('/workspace/')
241-
);
242-
if (!workspaceOnly)
243-
throw new Error('Expected a workspace-only manifest entry');
244-
cases.push(
245-
redirectCase(
246-
'workspace Docs serialization',
247-
`${workspaceOnly.legacyPath}?mode=docs`,
248-
entryResolution(workspaceOnly),
249-
'docs'
250-
)
251-
);
252-
253239
cases.push(
254240
notFoundCase('unknown path 404', '/unknown'),
255241
notFoundCase('partial path 404', '/langgraph/core-capabilities/streaming'),
@@ -285,13 +271,8 @@ const buildProductionCases = (): RedirectSmokeCase[] => {
285271
const docsBacked = cockpitManifest.find((entry) =>
286272
getWorkspaceDestinationPath(entry).startsWith('/docs/')
287273
);
288-
const workspaceOnly = cockpitManifest.find((entry) =>
289-
getWorkspaceDestinationPath(entry).startsWith('/workspace/')
290-
);
291-
if (!docsBacked || !workspaceOnly) {
292-
throw new Error(
293-
'Redirect smoke requires Docs-backed and workspace-only routes'
294-
);
274+
if (!docsBacked) {
275+
throw new Error('Redirect smoke requires a Docs-backed route');
295276
}
296277
return [
297278
redirectCase('root production redirect', '/', root, 'run'),
@@ -300,11 +281,6 @@ const buildProductionCases = (): RedirectSmokeCase[] => {
300281
docsBacked.legacyPath,
301282
entryResolution(docsBacked)
302283
),
303-
redirectCase(
304-
'workspace-only production redirect',
305-
workspaceOnly.legacyPath,
306-
entryResolution(workspaceOnly)
307-
),
308284
notFoundCase('unknown production 404', '/unknown'),
309285
{
310286
name: 'favicon production redirect',
@@ -442,9 +418,7 @@ const verifyCase = (
442418
if (response.status !== smokeCase.expectedStatus) {
443419
const protectionHint =
444420
response.status === 302 &&
445-
(response.headers.location ?? '').startsWith(
446-
'https://vercel.com/sso-api'
447-
)
421+
(response.headers.location ?? '').startsWith('https://vercel.com/sso-api')
448422
? ` The deployment answered with Vercel deployment protection, not the redirect service; supply the owning project's automation bypass secret via ${BYPASS_SECRET_ENV}.`
449423
: '';
450424
throw new RedirectContractError(

‎apps/cockpit/src/lib/cockpit-page.spec.ts‎

Lines changed: 3 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -266,15 +266,12 @@ describe('registry-derived legacy Website redirects', () => {
266266
).toBe(expectedHref(docsOnly, 'Docs'));
267267
});
268268

269-
it('serializes Docs mode truthfully for docs and workspace destinations', () => {
269+
it('serializes Docs mode truthfully for docs destinations', () => {
270270
const docsDestination = cockpitManifest.find((entry) =>
271271
getWorkspaceDestinationPath(entry).startsWith('/docs/')
272272
);
273-
const workspaceDestination = cockpitManifest.find((entry) =>
274-
getWorkspaceDestinationPath(entry).startsWith('/workspace/')
275-
);
276-
if (!docsDestination || !workspaceDestination) {
277-
throw new Error('Expected docs and workspace fixtures');
273+
if (!docsDestination) {
274+
throw new Error('Expected a docs fixture');
278275
}
279276

280277
expect(
@@ -284,14 +281,6 @@ describe('registry-derived legacy Website redirects', () => {
284281
productionEnvironment
285282
)
286283
).toBe(expectedHref(docsDestination, 'Docs'));
287-
expect(
288-
getLegacyWebsiteRedirect(
289-
workspaceDestination.legacyPath,
290-
['docs'],
291-
productionEnvironment
292-
)
293-
).toBe(expectedHref(workspaceDestination, 'Docs'));
294-
expect(expectedHref(workspaceDestination, 'Docs')).toContain('?mode=docs');
295284
});
296285

297286
it('returns null for unknown, partial, extra, malformed, and trailing paths', () => {
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
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.
3+
---
4+
5+
# Client Tools
6+
7+
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.
8+
9+
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).
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
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.
3+
---
4+
5+
# Generative UI
6+
7+
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.
8+
9+
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).
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
description: Show an AG-UI agent's delegated child runs as attributed subagent cards with their own tool calls and messages.
3+
---
4+
5+
# Subagents
6+
7+
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.
8+
9+
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).
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
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.
3+
---
4+
5+
# Tool Views
6+
7+
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.
8+
9+
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).
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
description: Resume a LangGraph run after a crash, restart, or redeploy from its last checkpoint, and offer retry from the Angular UI.
3+
---
4+
5+
# Durable Execution
6+
7+
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.
8+
9+
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).
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
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.
3+
---
4+
5+
# Repeat Loops
6+
7+
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.
8+
9+
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).

0 commit comments

Comments
 (0)