From 05d8b9efe3f8e0299dec6f4757a3c44b109cf556 Mon Sep 17 00:00:00 2001 From: Brian Love Date: Sat, 5 Sep 2026 09:51:37 -0700 Subject: [PATCH 1/3] test(website): guard user-facing copy against the retired product name Co-Authored-By: Claude Fable 5.1 --- .../src/lib/cockpit-retirement.spec.ts | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/apps/website/src/lib/cockpit-retirement.spec.ts b/apps/website/src/lib/cockpit-retirement.spec.ts index b9dee3ec4..c9e3208a2 100644 --- a/apps/website/src/lib/cockpit-retirement.spec.ts +++ b/apps/website/src/lib/cockpit-retirement.spec.ts @@ -223,4 +223,40 @@ describe('Cockpit surface retirement', () => { expect(post).not.toContain('alt="The cockpit ag-ui/interrupts welcome screen'); expect(post).toContain('alt="The Threadplane docs workspace'); }); + + it('keeps the retired product name out of user-facing Website copy', () => { + const USER_FACING = /\.(?:mdx|md|tsx)$/; + const ALLOWED_TOKENS = [ + /cockpit\/[a-z-]+\/[a-z-]+/g, // example repository paths + /apps\/cockpit\//g, // tooling paths (removed by Part C) + /@threadplane\/cockpit-[a-z-]+/g, // package names + /cockpit-[a-z-]+-angular/g, // Nx project names + /cockpit-(?:registry|shell|runtime-bridge|telemetry|docs|testing|ui)/g, + /cockpit_(?:did|cap|phk|host)/g, // runtime bridge query params + /Cockpit(?:ManifestEntry|ManifestIdentity)/g, // type names + /cockpit-retirement/g, // this guard's own file name + /cockpit-ag-ui-interrupts/g, // real project/app identifier (cockpit/ag-ui/interrupts) + /cockpit\/ports\.mjs/g, // real repo file path + /cockpit\/deep-agents/g, // real repo directory path + /cockpit\/runtimes\/\//g, // real repo directory path (templated) + /serve:cockpit\b/g, // real Nx target name + /\("cockpit", "deep-agents-memory"\)/g, // real LangGraph memory namespace literal + ]; + const sources = sourceFiles(WEBSITE_ROOT) + .filter((path) => USER_FACING.test(path)) + .map((path) => ({ + relativePath: relative(WEBSITE_ROOT, path), + content: readFileSync(path, 'utf8'), + })); + + const violations = sources.flatMap(({ relativePath, content }) => + content.split('\n').flatMap((line, index) => { + let scrubbed = line; + for (const token of ALLOWED_TOKENS) scrubbed = scrubbed.replace(token, ''); + return /cockpit/i.test(scrubbed) ? [`${relativePath}:${index + 1}`] : []; + }) + ); + + expect(violations).toEqual([]); + }); }); From 49588259112c5c417d2fb72d64e6e83c90f73d34 Mon Sep 17 00:00:00 2001 From: Brian Love Date: Sat, 5 Sep 2026 09:58:39 -0700 Subject: [PATCH 2/3] refactor(website): the workspace surface no longer says cockpit Co-Authored-By: Claude Fable 5.1 --- ...ming-chat-ui-in-angular-with-langgraph.mdx | 2 +- ...an-in-the-loop-ag-ui-agents-in-angular.mdx | 6 +- ...6-08-29-what-fixture-replay-cant-catch.mdx | 2 +- ...-what-changes-when-the-runtime-changes.mdx | 2 +- .../chat/getting-started/introduction.mdx | 2 +- .../docs/choosing-an-adapter/index.mdx | 2 +- .../e2e/custom-runtime-bfcache.spec.ts | 2 +- .../e2e/custom-runtime-targets.spec.ts | 2 +- apps/website/e2e/docs-shell.spec.ts | 2 +- apps/website/e2e/docs.spec.ts | 2 +- apps/website/e2e/nav-height.spec.ts | 2 +- .../e2e/platform-production-smoke.spec.ts | 2 +- apps/website/e2e/website.spec.ts | 8 +- apps/website/e2e/workspace-shell.spec.ts | 20 +- .../{cockpit-api.webp => workspace-api.webp} | Bin ...{cockpit-code.webp => workspace-code.webp} | Bin ...{cockpit-docs.webp => workspace-docs.webp} | Bin .../{cockpit-run.webp => workspace-run.webp} | Bin .../scripts/capture-screenshots.spec.ts | 10 +- apps/website/scripts/capture-screenshots.ts | 10 +- apps/website/src/app/pilot-to-prod/page.tsx | 2 +- apps/website/src/styles/docs.css | 4 +- .../src/styles/style-contracts.spec.ts | 2 +- .../lib/components/api-mode/api-mode.spec.tsx | 12 +- .../src/lib/components/api-mode/api-mode.tsx | 10 +- .../lib/components/code-mode/code-mode.tsx | 14 +- .../components/code-mode/file-tree.spec.tsx | 4 +- .../lib/components/code-mode/file-tree.tsx | 14 +- .../control-plane/activity-panel.spec.tsx | 6 +- .../cockpit-control-plane.spec.tsx | 12 +- .../control-plane/cockpit-control-plane.tsx | 20 +- .../control-plane/runtime-section.spec.tsx | 2 +- .../control-plane/runtime-section.tsx | 2 +- .../components/mobile-nav-overlay.spec.tsx | 30 +- .../src/lib/components/mobile-nav-overlay.tsx | 10 +- .../narrative-docs/narrative-docs.tsx | 2 +- .../components/sidebar/cockpit-sidebar.tsx | 4 +- .../sidebar/navigation-groups.spec.tsx | 20 +- .../components/sidebar/navigation-groups.tsx | 10 +- .../src/lib/workspace-shell.spec.tsx | 6 +- .../src/lib/workspace-shell.tsx | 14 +- libs/workspace-react/src/styles/workspace.css | 428 +++++++++--------- 42 files changed, 356 insertions(+), 348 deletions(-) rename apps/website/public/screenshots/{cockpit-api.webp => workspace-api.webp} (100%) rename apps/website/public/screenshots/{cockpit-code.webp => workspace-code.webp} (100%) rename apps/website/public/screenshots/{cockpit-docs.webp => workspace-docs.webp} (100%) rename apps/website/public/screenshots/{cockpit-run.webp => workspace-run.webp} (100%) diff --git a/apps/website/content/blog/2026-05-17-build-a-streaming-chat-ui-in-angular-with-langgraph.mdx b/apps/website/content/blog/2026-05-17-build-a-streaming-chat-ui-in-angular-with-langgraph.mdx index 239603abe..1ccd2d0f6 100644 --- a/apps/website/content/blog/2026-05-17-build-a-streaming-chat-ui-in-angular-with-langgraph.mdx +++ b/apps/website/content/blog/2026-05-17-build-a-streaming-chat-ui-in-angular-with-langgraph.mdx @@ -284,7 +284,7 @@ It is not the whole story. Three places to look next: -- The [cockpit chat example](/docs/chat/getting-started/quickstart) — a full multi-thread, multi-agent chat shell with real LangGraph graphs behind it. +- The [chat quickstart example](/docs/chat/getting-started/quickstart) — a full multi-thread, multi-agent chat shell with real LangGraph graphs behind it. - The [persistence guide](/docs/chat/guides/lifecycle) — how to manage threads, runs, and resume state across sessions. - The [interrupts guide](/docs/chat/guides/streaming) — how to handle human-in-the-loop pauses without breaking the streaming model. diff --git a/apps/website/content/blog/2026-06-04-human-in-the-loop-ag-ui-agents-in-angular.mdx b/apps/website/content/blog/2026-06-04-human-in-the-loop-ag-ui-agents-in-angular.mdx index f7c82f2e3..aede30758 100644 --- a/apps/website/content/blog/2026-06-04-human-in-the-loop-ag-ui-agents-in-angular.mdx +++ b/apps/website/content/blog/2026-06-04-human-in-the-loop-ag-ui-agents-in-angular.mdx @@ -72,7 +72,7 @@ The `` you build today survives the backend you swap in next Three pieces: -- **AG-UI-fronted LangGraph backend.** The same compiled graph as the LangGraph post, duplicated under `cockpit/ag-ui/interrupts/python` because cockpit examples are standalone. +- **AG-UI-fronted LangGraph backend.** The same compiled graph as the LangGraph post, duplicated under `cockpit/ag-ui/interrupts/python` because example apps are standalone. Wrapped with `ag-ui-langgraph`'s `LangGraphAgent(name, graph)` + `add_langgraph_fastapi_endpoint(app, agent, path='/agent')` and served by `uvicorn`. When `interrupt()` fires inside the graph, `ag-ui-langgraph` emits a `CUSTOM` AG-UI event with `name: 'on_interrupt'` and a `value` carrying the interrupt payload — serialized as a JSON string via `dump_json_safe`. The uvicorn server needs a `MemorySaver` checkpointer (`langgraph dev` and LangGraph Platform inject one automatically; plain uvicorn does not, and `aget_state` raises "No checkpointer set" without it). @@ -166,7 +166,7 @@ def request_approval(state: RefundState) -> dict: } ``` -This file is **duplicated** into `cockpit/ag-ui/interrupts/python/src/graph.py` per the cockpit standalone-examples convention — copy, do not import across examples. +This file is **duplicated** into `cockpit/ag-ui/interrupts/python/src/graph.py` per the standalone-examples convention — copy, do not import across examples. The graph itself does not know it will be served over AG-UI. @@ -246,7 +246,7 @@ const WELCOME_SUGGESTIONS = [ ] as const; /** - * Refund authorization cockpit example. + * Refund authorization example. * * The LangGraph backend acknowledges the refund draft, then pauses at * `request_approval` with a structured interrupt payload of the form diff --git a/apps/website/content/blog/2026-08-29-what-fixture-replay-cant-catch.mdx b/apps/website/content/blog/2026-08-29-what-fixture-replay-cant-catch.mdx index 3791b7288..c66c27d3d 100644 --- a/apps/website/content/blog/2026-08-29-what-fixture-replay-cant-catch.mdx +++ b/apps/website/content/blog/2026-08-29-what-fixture-replay-cant-catch.mdx @@ -54,7 +54,7 @@ It cannot tell you that your graph's conditional edge routes correctly, that you Push the seam out to the provider and all of that is under test, because none of it was replaced. Replacing the model — and only the model — is also what makes it cheap enough to run everywhere: no API spend, no rate limits, no coin flips. -We have 53 fixture files holding 169 entries across 36 apps — 34 cockpit capabilities and two example apps — all on the same harness. +We have 53 fixture files holding 169 entries across 36 apps — 34 workspace capabilities and two example apps — all on the same harness. This is the outer tier. For in-process fakes at the unit level, the [testing guide](/docs/langgraph/guides/testing) covers `provideFakeAgent()`, `mockLangGraphAgent()`, and `MockAgentTransport`, which are a different tool for a different job. diff --git a/apps/website/content/blog/2026-08-31-what-changes-when-the-runtime-changes.mdx b/apps/website/content/blog/2026-08-31-what-changes-when-the-runtime-changes.mdx index c8efdb8f1..9652905cd 100644 --- a/apps/website/content/blog/2026-08-31-what-changes-when-the-runtime-changes.mdx +++ b/apps/website/content/blog/2026-08-31-what-changes-when-the-runtime-changes.mdx @@ -174,7 +174,7 @@ Anything reaching for `AgentWithHistory` — or for the `langGraph*`-prefixed me ### The test harness forks -Each cockpit pair uses a different end-to-end harness entry point: `createGlobalSetup` for the LangGraph twin, `createAgUiGlobalSetup` for the AG-UI twin. +Each workspace pair uses a different end-to-end harness entry point: `createGlobalSetup` for the LangGraph twin, `createAgUiGlobalSetup` for the AG-UI twin. Different backend process, different port wiring, different fixture replay. A runtime swap is a test-infrastructure change, not only an app change. diff --git a/apps/website/content/docs/chat/getting-started/introduction.mdx b/apps/website/content/docs/chat/getting-started/introduction.mdx index d0a51debf..81680dcd5 100644 --- a/apps/website/content/docs/chat/getting-started/introduction.mdx +++ b/apps/website/content/docs/chat/getting-started/introduction.mdx @@ -44,7 +44,7 @@ Compositions are opinionated, styled components that combine primitives into rea | `ChatInterruptPanelComponent` | `chat-interrupt-panel` | Styled interrupt card with accept/edit/respond/ignore actions | | `ChatToolCallCardComponent` | `chat-tool-call-card` | Expandable card showing tool name, inputs, and output | | `ChatSubagentCardComponent` | `chat-subagent-card` | Expandable card showing subagent status and messages | -| `ChatDebugComponent` | `chat-debug` | Debug cockpit from `@threadplane/chat/debug` with timeline, state inspector, and diff viewer | +| `ChatDebugComponent` | `chat-debug` | Debug panel from `@threadplane/chat/debug` with timeline, state inspector, and diff viewer | | `ChatTimelineSliderComponent` | `chat-timeline-slider` | Slider-based timeline navigation | ## How the Stack Fits Together diff --git a/apps/website/content/docs/choosing-an-adapter/index.mdx b/apps/website/content/docs/choosing-an-adapter/index.mdx index 8999c7df9..52d9bfa61 100644 --- a/apps/website/content/docs/choosing-an-adapter/index.mdx +++ b/apps/website/content/docs/choosing-an-adapter/index.mdx @@ -80,7 +80,7 @@ Three layers of test doubles, smallest scope first: |---|---|---|---| | **Contract mock** | `mockAgent()` (chat) / `mockLangGraphAgent()` (langgraph) | nothing — you set `messages()`, `status()` directly | component/unit tests | | **Fake backend** | `provideFakeAgent({ tokens })` (both adapters) | the real adapter pipeline; canned wire events | adapter-integration tests, in-browser, no server | -| **LLM fixture replay** | aimock (cockpit/examples e2e) | the whole stack incl. a real graph; only the LLM is replayed | full end-to-end | +| **LLM fixture replay** | aimock (workspace examples e2e) | the whole stack incl. a real graph; only the LLM is replayed | full end-to-end | Reach for the smallest layer that covers your test. Don't stand up aimock when a `mockAgent` unit test suffices; don't hand-roll a transport when `provideFakeAgent` exists. diff --git a/apps/website/e2e/custom-runtime-bfcache.spec.ts b/apps/website/e2e/custom-runtime-bfcache.spec.ts index 9810ecd82..e80c08684 100644 --- a/apps/website/e2e/custom-runtime-bfcache.spec.ts +++ b/apps/website/e2e/custom-runtime-bfcache.spec.ts @@ -5,7 +5,7 @@ const FIXTURE_ORIGIN = 'http://127.0.0.1:4399'; const FIXTURE_KEY = 'test-key-redact-me'; const desktopControlPlane = (page: Page) => - page.locator('[data-cockpit-desktop-navigation]'); + page.locator('[data-workspace-desktop-navigation]'); async function openSettings(page: Page): Promise { await desktopControlPlane(page) diff --git a/apps/website/e2e/custom-runtime-targets.spec.ts b/apps/website/e2e/custom-runtime-targets.spec.ts index 613ba88a7..c8e7fd944 100644 --- a/apps/website/e2e/custom-runtime-targets.spec.ts +++ b/apps/website/e2e/custom-runtime-targets.spec.ts @@ -73,7 +73,7 @@ function observePrivacyChannels(page: Page): { } const desktopControlPlane = (page: Page) => - page.locator('[data-cockpit-desktop-navigation]'); + page.locator('[data-workspace-desktop-navigation]'); const runtimeFrame = (page: Page, title: string): FrameLocator => page.frameLocator(`iframe[title="${title} live example"]`); diff --git a/apps/website/e2e/docs-shell.spec.ts b/apps/website/e2e/docs-shell.spec.ts index 3e5e4a973..008d16ce8 100644 --- a/apps/website/e2e/docs-shell.spec.ts +++ b/apps/website/e2e/docs-shell.spec.ts @@ -105,7 +105,7 @@ test.describe('docs shell layout', () => { parseFloat(getComputedStyle(document.documentElement).getPropertyValue('--nav-h')), ); const tops = async () => ({ - plane: await page.locator('[data-cockpit-desktop-navigation]').evaluate((el) => Math.round(el.getBoundingClientRect().top)), + plane: await page.locator('[data-workspace-desktop-navigation]').evaluate((el) => Math.round(el.getBoundingClientRect().top)), toc: await page.locator('.docs-toc').evaluate((el) => Math.round(el.getBoundingClientRect().top)), }); diff --git a/apps/website/e2e/docs.spec.ts b/apps/website/e2e/docs.spec.ts index ab89f271e..84d968a0f 100644 --- a/apps/website/e2e/docs.spec.ts +++ b/apps/website/e2e/docs.spec.ts @@ -60,7 +60,7 @@ test.describe('Docs slug page', () => { ); const pane = page.locator( - '[data-cockpit-desktop-navigation] [data-control-plane-pane]', + '[data-workspace-desktop-navigation] [data-control-plane-pane]', ); // Search moved to the top of the pane (was the bottom Actions-bar item // this test used to focus), so it can no longer stand in for "something diff --git a/apps/website/e2e/nav-height.spec.ts b/apps/website/e2e/nav-height.spec.ts index d60b5ec70..ac17e7a9c 100644 --- a/apps/website/e2e/nav-height.spec.ts +++ b/apps/website/e2e/nav-height.spec.ts @@ -57,7 +57,7 @@ test('the docs column starts directly under the nav at a tablet width', async ({ .first() .evaluate((el) => el.getBoundingClientRect().bottom); const shellTop = await page - .locator('.website-workspace-host .cockpit-shell') + .locator('.website-workspace-host .workspace-shell') .evaluate((el) => el.getBoundingClientRect().top); expect(Math.abs(shellTop - navBottom)).toBeLessThanOrEqual(1); diff --git a/apps/website/e2e/platform-production-smoke.spec.ts b/apps/website/e2e/platform-production-smoke.spec.ts index 03f9172ce..89b473714 100644 --- a/apps/website/e2e/platform-production-smoke.spec.ts +++ b/apps/website/e2e/platform-production-smoke.spec.ts @@ -285,7 +285,7 @@ test.describe('Production: unified runtime embedding policy', () => { }); await page.goto(`${WEBSITE_URL}/docs/langgraph/guides/streaming?mode=run`); - const controls = page.locator('[data-cockpit-desktop-navigation]'); + const controls = page.locator('[data-workspace-desktop-navigation]'); await controls .getByRole('button', { name: 'Settings', exact: true }) .click(); diff --git a/apps/website/e2e/website.spec.ts b/apps/website/e2e/website.spec.ts index 76bb1f9c8..dcd275fcd 100644 --- a/apps/website/e2e/website.spec.ts +++ b/apps/website/e2e/website.spec.ts @@ -262,7 +262,7 @@ for (const viewport of [ ); await expectNoHorizontalOverflow(page, `Docs at ${viewport.width}px`); - const desktopControlPlane = page.locator('[data-cockpit-desktop-navigation]'); + const desktopControlPlane = page.locator('[data-workspace-desktop-navigation]'); await expect(page.getByRole('button', { name: 'Open menu' })).toBeHidden(); if (viewport.width >= 1024) { await expect(desktopControlPlane).toBeVisible(); @@ -306,7 +306,7 @@ for (const viewport of [ name: 'Documentation control plane', }); await expect(dialog).toBeVisible(); - await expect(page.locator('[data-cockpit-workspace]')).toHaveAttribute('inert', ''); + await expect(page.locator('[data-workspace-surface]')).toHaveAttribute('inert', ''); await expect(page.locator('nav.nav-bar')).toHaveAttribute('inert', ''); const close = dialog.getByRole('button', { name: 'Close navigation' }); @@ -338,7 +338,7 @@ test('docs forced colors preserve control boundaries and keyboard focus', async ); const run = page - .locator('[data-cockpit-desktop-navigation]') + .locator('[data-workspace-desktop-navigation]') .getByRole('button', { name: 'Run', exact: true }); await run.focus(); const styles = await run.evaluate((element) => { @@ -371,7 +371,7 @@ test('docs reduced motion disables mobile drawer transitions and animations', as }); await expect(overlay).toBeVisible(); const motion = await overlay.evaluate((element) => { - const panel = element.querySelector('.cockpit-mobile-control-plane-panel'); + const panel = element.querySelector('.workspace-mobile-control-plane-panel'); const overlayStyle = getComputedStyle(element); const panelStyle = panel ? getComputedStyle(panel) : null; return { diff --git a/apps/website/e2e/workspace-shell.spec.ts b/apps/website/e2e/workspace-shell.spec.ts index cda048129..f9cba878a 100644 --- a/apps/website/e2e/workspace-shell.spec.ts +++ b/apps/website/e2e/workspace-shell.spec.ts @@ -15,7 +15,7 @@ declare global { } const modeButton = (page: Page, mode: 'Docs' | 'Run' | 'Code' | 'API') => - page.locator('[data-cockpit-desktop-navigation]').getByRole('button', { + page.locator('[data-workspace-desktop-navigation]').getByRole('button', { name: mode === 'Run' ? RUN_RAIL_ITEM : mode, exact: mode !== 'Run', }); @@ -269,7 +269,7 @@ test.describe('workspace shell', () => { await expectMode(page, 'Docs'); await expect(shell).toHaveAttribute('data-e2e-shell-lifetime', 'original'); await page - .locator('[data-cockpit-desktop-navigation]') + .locator('[data-workspace-desktop-navigation]') .getByRole('button', { name: 'Activity', exact: true }) .click(); await expect(page.getByText('Mode changed to Code')).toBeVisible(); @@ -277,7 +277,7 @@ test.describe('workspace shell', () => { page.locator('[data-activity-capability]').first() ).toContainText('streaming'); await page - .locator('[data-cockpit-desktop-navigation]') + .locator('[data-workspace-desktop-navigation]') .getByRole('button', { name: 'Activity', exact: true }) .click(); await page.goBack(); @@ -436,7 +436,7 @@ test.describe('workspace shell', () => { await page.goto(streamingDocsPath); await expectNoHorizontalOverflow(page, 'desktop workspace'); - const desktop = page.locator('[data-cockpit-desktop-navigation]'); + const desktop = page.locator('[data-workspace-desktop-navigation]'); await expect(desktop).toBeVisible(); await expect(desktop.locator('[data-control-plane-rail]')).toBeVisible(); await expect(desktop.locator('[data-control-plane-pane]')).toBeVisible(); @@ -463,7 +463,7 @@ test.describe('workspace shell', () => { await expectNoHorizontalOverflow(page, `Website at ${viewport.width}px`); const desktopNavigation = page.locator( - '[data-cockpit-desktop-navigation]' + '[data-workspace-desktop-navigation]' ); const mobileTrigger = page.getByRole('button', { name: 'Open navigation', @@ -536,7 +536,7 @@ test.describe('workspace shell', () => { await page.goto(streamingDocsPath); await expectNoHorizontalOverflow(page, 'tablet workspace'); - const desktop = page.locator('[data-cockpit-desktop-navigation]'); + const desktop = page.locator('[data-workspace-desktop-navigation]'); await expect(desktop.locator('[data-control-plane-rail]')).toBeVisible(); await expect(desktop.locator('[data-control-plane-pane]')).toBeHidden(); const contextTrigger = page.getByRole('button', { name: 'Open context' }); @@ -586,7 +586,7 @@ test.describe('workspace shell', () => { await expectNoHorizontalOverflow(page, 'mobile workspace'); await expect( - page.locator('[data-cockpit-desktop-navigation]') + page.locator('[data-workspace-desktop-navigation]') ).toBeHidden(); await expect(page.getByRole('button', { name: 'Open menu' })).toBeHidden(); const trigger = page.getByRole('button', { name: 'Open navigation' }); @@ -629,7 +629,7 @@ test.describe('workspace shell', () => { }); const lateAnnouncement = page.locator('[data-late-announcement]'); await expect(lateAnnouncement).toBeHidden(); - await expect(page.locator('[data-cockpit-workspace]')).toHaveAttribute( + await expect(page.locator('[data-workspace-surface]')).toHaveAttribute( 'inert', '' ); @@ -789,7 +789,7 @@ test.describe('workspace shell', () => { await expect(dialog).toBeVisible(); const motion = await dialog.evaluate((element) => { const panel = element.querySelector( - '.cockpit-mobile-control-plane-panel' + '.workspace-mobile-control-plane-panel' ); const overlayStyle = getComputedStyle(element); const panelStyle = panel ? getComputedStyle(panel) : null; @@ -800,7 +800,7 @@ test.describe('workspace shell', () => { panelTransition: panelStyle?.transitionDuration, }; }); - const loader = dialog.locator('.cockpit-runtime-status-loader'); + const loader = dialog.locator('.workspace-runtime-status-loader'); await expect(loader).toBeVisible(); expect( await loader.evaluate( diff --git a/apps/website/public/screenshots/cockpit-api.webp b/apps/website/public/screenshots/workspace-api.webp similarity index 100% rename from apps/website/public/screenshots/cockpit-api.webp rename to apps/website/public/screenshots/workspace-api.webp diff --git a/apps/website/public/screenshots/cockpit-code.webp b/apps/website/public/screenshots/workspace-code.webp similarity index 100% rename from apps/website/public/screenshots/cockpit-code.webp rename to apps/website/public/screenshots/workspace-code.webp diff --git a/apps/website/public/screenshots/cockpit-docs.webp b/apps/website/public/screenshots/workspace-docs.webp similarity index 100% rename from apps/website/public/screenshots/cockpit-docs.webp rename to apps/website/public/screenshots/workspace-docs.webp diff --git a/apps/website/public/screenshots/cockpit-run.webp b/apps/website/public/screenshots/workspace-run.webp similarity index 100% rename from apps/website/public/screenshots/cockpit-run.webp rename to apps/website/public/screenshots/workspace-run.webp diff --git a/apps/website/scripts/capture-screenshots.spec.ts b/apps/website/scripts/capture-screenshots.spec.ts index f268023b8..2ca7b160f 100644 --- a/apps/website/scripts/capture-screenshots.spec.ts +++ b/apps/website/scripts/capture-screenshots.spec.ts @@ -39,7 +39,7 @@ describe('Website workspace screenshot capture', () => { expect(WORKSPACE_READY_SELECTOR).toBe( '[data-workspace-shell][data-hydrated="true"]' ); - expect(WORKSPACE_CONTENT_SELECTOR).toBe('[data-cockpit-workspace]'); + expect(WORKSPACE_CONTENT_SELECTOR).toBe('[data-workspace-surface]'); expect(workspaceModeSelector('Code')).toBe( '[data-workspace-shell][data-workspace-mode="Code"]' ); @@ -52,10 +52,10 @@ describe('Website workspace screenshot capture', () => { it('preserves all four modes and output basenames', () => { expect(CAPTURE_TARGETS.map(({ name, mode }) => ({ name, mode }))).toEqual([ - { name: 'cockpit-run', mode: 'Run' }, - { name: 'cockpit-code', mode: 'Code' }, - { name: 'cockpit-docs', mode: 'Docs' }, - { name: 'cockpit-api', mode: 'API' }, + { name: 'workspace-run', mode: 'Run' }, + { name: 'workspace-code', mode: 'Code' }, + { name: 'workspace-docs', mode: 'Docs' }, + { name: 'workspace-api', mode: 'API' }, ]); }); diff --git a/apps/website/scripts/capture-screenshots.ts b/apps/website/scripts/capture-screenshots.ts index 09d46c2e4..f467868d5 100644 --- a/apps/website/scripts/capture-screenshots.ts +++ b/apps/website/scripts/capture-screenshots.ts @@ -29,7 +29,7 @@ export const DEFAULT_WEBSITE_URL = 'https://threadplane.ai/docs/langgraph/guides/streaming?mode=run'; export const WORKSPACE_READY_SELECTOR = '[data-workspace-shell][data-hydrated="true"]'; -export const WORKSPACE_CONTENT_SELECTOR = '[data-cockpit-workspace]'; +export const WORKSPACE_CONTENT_SELECTOR = '[data-workspace-surface]'; export interface CaptureTarget { /** Output filename (without extension). */ @@ -48,16 +48,16 @@ export interface CaptureTarget { export const CAPTURE_TARGETS: readonly CaptureTarget[] = [ // Hero collage back frame + Stream FeatureBlock + Pilot "Build" block. // The "Run" mode shows the live chat surface — captures real product UI. - { name: 'cockpit-run', mode: 'Run', settleMs: 4000 }, + { name: 'workspace-run', mode: 'Run', settleMs: 4000 }, // Hero collage front frame replacement — Code mode shows the agent // source code in a tabbed code panel. - { name: 'cockpit-code', mode: 'Code', settleMs: 1500 }, + { name: 'workspace-code', mode: 'Code', settleMs: 1500 }, // Render FeatureBlock visual — Docs mode shows narrative documentation // (well-structured content, looks like rendered output). - { name: 'cockpit-docs', mode: 'Docs', settleMs: 1500 }, + { name: 'workspace-docs', mode: 'Docs', settleMs: 1500 }, // API mode shows the API reference renderer — useful alternative // for the Render block visual. - { name: 'cockpit-api', mode: 'API', settleMs: 1500 }, + { name: 'workspace-api', mode: 'API', settleMs: 1500 }, ]; export interface CaptureArgs { diff --git a/apps/website/src/app/pilot-to-prod/page.tsx b/apps/website/src/app/pilot-to-prod/page.tsx index a940f7a47..0f380cec4 100644 --- a/apps/website/src/app/pilot-to-prod/page.tsx +++ b/apps/website/src/app/pilot-to-prod/page.tsx @@ -108,7 +108,7 @@ export default function PilotToProdPage() { visual={ Threadplane Website workspace — live chat surface ready to receive a message { ); expect(heading.className).toMatch(/(?:^|\s)font-sans(?:\s|$)/); expect(heading.className).toMatch(/(?:^|\s)tracking-normal(?:\s|$)/); - expect(heading.className).toMatch(/(?:^|\s)cockpit-api-heading(?:\s|$)/); + expect(heading.className).toMatch( + /(?:^|\s)workspace-api-heading(?:\s|$)/ + ); } expect(screen.getByRole('heading', { name: 'stream' }).className).toMatch( @@ -116,7 +118,7 @@ describe('ApiMode', () => { 'utf8' ); expect(css).toMatch( - /\.cockpit-prose \.cockpit-api-heading\s*\{[\s\S]*font-family:\s*var\(--font-inter\),\s*var\(--ds-font-sans\)[\s\S]*letter-spacing:\s*normal/ + /\.workspace-prose \.workspace-api-heading\s*\{[\s\S]*font-family:\s*var\(--font-inter\),\s*var\(--ds-font-sans\)[\s\S]*letter-spacing:\s*normal/ ); }); @@ -131,8 +133,10 @@ describe('ApiMode', () => { 'utf8' ); - expect(css).toContain('.cockpit-shell pre.shiki'); - expect(css).toMatch(/\[data-theme=['"]dark['"]\] \.cockpit-shell \.shiki/); + expect(css).toContain('.workspace-shell pre.shiki'); + expect(css).toMatch( + /\[data-theme=['"]dark['"]\] \.workspace-shell \.shiki/ + ); expect(css).not.toMatch(/(?:^|\n)pre\.shiki\s*\{/); expect(css).not.toMatch(/\[data-theme=['"]dark['"]\] \.shiki[,\s{]/); }); diff --git a/libs/workspace-react/src/lib/components/api-mode/api-mode.tsx b/libs/workspace-react/src/lib/components/api-mode/api-mode.tsx index d1b7c409e..d4b3a158c 100644 --- a/libs/workspace-react/src/lib/components/api-mode/api-mode.tsx +++ b/libs/workspace-react/src/lib/components/api-mode/api-mode.tsx @@ -72,7 +72,7 @@ function DocArticle({ section }: { section: DocSection }) { {section.params.length > 0 ? (
Parameters @@ -124,7 +124,7 @@ function DocArticle({ section }: { section: DocSection }) { {section.returns ? (
Returns @@ -168,11 +168,11 @@ export function ApiMode({ docSections, hasCodeFiles = false }: ApiModeProps) { aria-label="API mode" className="h-full overflow-auto py-6 px-4 md:px-8" > -
+
{tsSections.length > 0 ? (

{LANGUAGE_LABELS[tsSections[0]?.language] ?? 'TypeScript'} @@ -189,7 +189,7 @@ export function ApiMode({ docSections, hasCodeFiles = false }: ApiModeProps) { {pySections.length > 0 ? (

{LANGUAGE_LABELS[pySections[0]?.language] ?? 'Python'} diff --git a/libs/workspace-react/src/lib/components/code-mode/code-mode.tsx b/libs/workspace-react/src/lib/components/code-mode/code-mode.tsx index 9fda5ca12..af075a99e 100644 --- a/libs/workspace-react/src/lib/components/code-mode/code-mode.tsx +++ b/libs/workspace-react/src/lib/components/code-mode/code-mode.tsx @@ -23,9 +23,9 @@ function CodeFileContent({ content: string | undefined; }) { if (!content) { - return

No source available for {getTabLabel(path)}

; + return

No source available for {getTabLabel(path)}

; } - return
; + return
; } export function CodeMode({ entryTitle, codeAssetPaths, backendAssetPaths, codeFiles, promptFiles, capability }: CodeModeProps) { @@ -154,8 +154,8 @@ export function CodeMode({ entryTitle, codeAssetPaths, backendAssetPaths, codeFi value={path} className={ isPromptPath(path) - ? 'text-[var(--ds-accent)]/70 data-[state=active]:text-[var(--ds-accent)] cockpit-tab-trigger' - : 'cockpit-tab-trigger' + ? 'text-[var(--ds-accent)]/70 data-[state=active]:text-[var(--ds-accent)] workspace-tab-trigger' + : 'workspace-tab-trigger' } > {label} @@ -173,7 +173,7 @@ export function CodeMode({ entryTitle, codeAssetPaths, backendAssetPaths, codeFi handleClose(path); } }} - className="cockpit-tab-trigger__close" + className="workspace-tab-trigger__close" >× ); @@ -191,9 +191,9 @@ export function CodeMode({ entryTitle, codeAssetPaths, backendAssetPaths, codeFi return ( {content ? ( -
{content}
+
{content}
) : ( -

No content for {getTabLabel(path)}

+

No content for {getTabLabel(path)}

)}
); diff --git a/libs/workspace-react/src/lib/components/code-mode/file-tree.spec.tsx b/libs/workspace-react/src/lib/components/code-mode/file-tree.spec.tsx index 1dacd492c..a5f0b3489 100644 --- a/libs/workspace-react/src/lib/components/code-mode/file-tree.spec.tsx +++ b/libs/workspace-react/src/lib/components/code-mode/file-tree.spec.tsx @@ -62,10 +62,10 @@ describe('FileTree', () => { 'utf8' ); const rowRule = css.match( - /\.cockpit-file-tree__file,\s*\.cockpit-file-tree__folder\s*\{([^}]*)\}/ + /\.workspace-file-tree__file,\s*\.workspace-file-tree__folder\s*\{([^}]*)\}/ )?.[1]; const activeRule = css.match( - /\.cockpit-file-tree__file\[aria-current=["']true["']\]\s*\{([^}]*)\}/ + /\.workspace-file-tree__file\[aria-current=["']true["']\]\s*\{([^}]*)\}/ )?.[1]; expect(rowRule).not.toMatch(/border-left/); diff --git a/libs/workspace-react/src/lib/components/code-mode/file-tree.tsx b/libs/workspace-react/src/lib/components/code-mode/file-tree.tsx index 17f152934..3d0f75c29 100644 --- a/libs/workspace-react/src/lib/components/code-mode/file-tree.tsx +++ b/libs/workspace-react/src/lib/components/code-mode/file-tree.tsx @@ -29,7 +29,7 @@ export function FileTree({ paths, activePath, onSelect }: FileTreeProps) { }, []); return ( -
    +
      {tree.map((node, i) => ( onSelect(node.path)} - className="cockpit-file-tree__file" + className="workspace-file-tree__file" style={{ paddingLeft: `${0.75 + depth * 0.75}rem` }} > - {node.label} - {chip ? : null} + {node.label} + {chip ? : null} ); @@ -86,18 +86,18 @@ function Node({ node, depth, folderId, activePath, collapsedFolders, onToggleFol data-folder-row aria-expanded={!isCollapsed} onClick={() => onToggleFolder(folderId)} - className="cockpit-file-tree__folder" + className="workspace-file-tree__folder" style={{ paddingLeft: `${0.5 + depth * 0.75}rem` }} > - {folder.label} + {folder.label} {!isCollapsed ? (
        diff --git a/libs/workspace-react/src/lib/components/control-plane/activity-panel.spec.tsx b/libs/workspace-react/src/lib/components/control-plane/activity-panel.spec.tsx index 964b71091..04b25981d 100644 --- a/libs/workspace-react/src/lib/components/control-plane/activity-panel.spec.tsx +++ b/libs/workspace-react/src/lib/components/control-plane/activity-panel.spec.tsx @@ -55,12 +55,12 @@ describe('ActivityPanel', () => { it('styles stable timeline hooks with neutral, error, and recovery states', () => { expect(cockpitCss).toMatch(/\[data-activity-connector\]/); expect(cockpitCss).toMatch( - /\[data-activity-severity=["']error["']\][\s\S]*?var\(--cockpit-state-error/ + /\[data-activity-severity=["']error["']\][\s\S]*?var\(--workspace-state-error/ ); expect(cockpitCss).toMatch( - /\[data-activity-kind=["']runtime_recovered["']\][\s\S]*?var\(--cockpit-state-success/ + /\[data-activity-kind=["']runtime_recovered["']\][\s\S]*?var\(--workspace-state-success/ ); - expect(cockpitCss).toMatch(/\[data-cockpit-activity-attention\]/); + expect(cockpitCss).toMatch(/\[data-workspace-activity-attention\]/); }); it('uses compact local time while preserving machine and accessible datetimes', () => { diff --git a/libs/workspace-react/src/lib/components/control-plane/cockpit-control-plane.spec.tsx b/libs/workspace-react/src/lib/components/control-plane/cockpit-control-plane.spec.tsx index 7373faf7f..96a1e2eca 100644 --- a/libs/workspace-react/src/lib/components/control-plane/cockpit-control-plane.spec.tsx +++ b/libs/workspace-react/src/lib/components/control-plane/cockpit-control-plane.spec.tsx @@ -140,8 +140,8 @@ describe('CockpitControlPlane', () => { it('keeps context headings on the shared sentence-case sans contract', () => { for (const selector of [ - '[data-cockpit-context-content] [data-control-plane-section-trigger]', - '[data-cockpit-context-content] [data-control-plane-section-heading]', + '[data-workspace-context-content] [data-control-plane-section-trigger]', + '[data-workspace-context-content] [data-control-plane-section-heading]', ]) { const declarations = declarationsFor(cockpitCss, selector); expect(declarations).toMatch( @@ -238,7 +238,7 @@ describe('CockpitControlPlane', () => { utilities.map((button) => button.getAttribute('aria-label')) ).toEqual(['Activity, 1 unread problem', 'Settings']); expect( - document.querySelector('[data-cockpit-activity-attention]')?.textContent + document.querySelector('[data-workspace-activity-attention]')?.textContent ).toBe(''); fireEvent.click( screen.getByRole('button', { name: 'Activity, 1 unread problem' }) @@ -350,13 +350,13 @@ describe('CockpitControlPlane', () => { // hover, which in dark is rgb(28,28,28) inside the rail's rgb(44,44,44) // -- a fixed ring would read as a lighter halo whenever Run is hovered. expect(cockpitCss).toMatch( - /\[data-control-plane-rail-status\]\s*\{[^}]*border:\s*2px solid var\(--cockpit-rail-status-ring\)/ + /\[data-control-plane-rail-status\]\s*\{[^}]*border:\s*2px solid var\(--workspace-rail-status-ring\)/ ); expect(cockpitCss).toMatch( - /\[data-control-plane-rail-item\]\s*\{[^}]*--cockpit-rail-status-ring:\s*var\(--ds-surface-tinted\)/ + /\[data-control-plane-rail-item\]\s*\{[^}]*--workspace-rail-status-ring:\s*var\(--ds-surface-tinted\)/ ); expect(cockpitCss).toMatch( - /\[data-control-plane-rail-item\][^{]*:hover\s*\{[^}]*--cockpit-rail-status-ring:\s*var\(--ds-surface\)/ + /\[data-control-plane-rail-item\][^{]*:hover\s*\{[^}]*--workspace-rail-status-ring:\s*var\(--ds-surface\)/ ); // Forced colors overrides background, so the dot needs an explicit // treatment like the runtime pill it sits beside. diff --git a/libs/workspace-react/src/lib/components/control-plane/cockpit-control-plane.tsx b/libs/workspace-react/src/lib/components/control-plane/cockpit-control-plane.tsx index 1f14a46d1..c016babc1 100644 --- a/libs/workspace-react/src/lib/components/control-plane/cockpit-control-plane.tsx +++ b/libs/workspace-react/src/lib/components/control-plane/cockpit-control-plane.tsx @@ -214,7 +214,7 @@ export function CockpitControlPlane({ title="Settings" onClose={() => closeUtility('settings', settingsRef)} > -
        +
        Language {entry ? ( {themeControl ? ( -
        +
        Theme {themeControl}
        @@ -283,8 +283,8 @@ export function CockpitControlPlane({ return (
        {layout === 'full' ? ( @@ -306,18 +306,18 @@ export function CockpitControlPlane({ <> + { /@media \(forced-colors:\s*active\)[\s\S]*?\[data-runtime-status\]\[data-runtime-phase\]\s*\{[\s\S]*?color:\s*CanvasText;/ ); expect(cockpitCss).toMatch( - /@media \(prefers-reduced-motion:\s*reduce\)[\s\S]*?\.cockpit-runtime-status-loader[\s\S]*?animation:\s*none/ + /@media \(prefers-reduced-motion:\s*reduce\)[\s\S]*?\.workspace-runtime-status-loader[\s\S]*?animation:\s*none/ ); }); diff --git a/libs/workspace-react/src/lib/components/control-plane/runtime-section.tsx b/libs/workspace-react/src/lib/components/control-plane/runtime-section.tsx index a3f2df2de..6cf0e9825 100644 --- a/libs/workspace-react/src/lib/components/control-plane/runtime-section.tsx +++ b/libs/workspace-react/src/lib/components/control-plane/runtime-section.tsx @@ -126,7 +126,7 @@ function RuntimeStatus({ phase }: { phase: RuntimePhase }) { aria-hidden="true" className={ presentation.icon === LoaderCircle - ? 'cockpit-runtime-status-loader' + ? 'workspace-runtime-status-loader' : undefined } /> diff --git a/libs/workspace-react/src/lib/components/mobile-nav-overlay.spec.tsx b/libs/workspace-react/src/lib/components/mobile-nav-overlay.spec.tsx index d9537a949..e78b2be63 100644 --- a/libs/workspace-react/src/lib/components/mobile-nav-overlay.spec.tsx +++ b/libs/workspace-react/src/lib/components/mobile-nav-overlay.spec.tsx @@ -220,7 +220,9 @@ describe('MobileNavOverlay', () => { it('renders the tablet context as a pane-only, non-mobile one-column control plane', () => { renderOverlay({ variant: 'tablet', controlPlaneLayout: 'pane' }); const overlay = dialog(); - const controlPlane = overlay.querySelector('[data-cockpit-control-plane]'); + const controlPlane = overlay.querySelector( + '[data-workspace-control-plane]' + ); expect(controlPlane?.getAttribute('data-mobile')).toBeNull(); expect( @@ -774,7 +776,7 @@ describe('MobileNavOverlay', () => { expect( within(dialog()).getByRole('button', { name: 'Close navigation' }) .classList - ).toContain('cockpit-mobile-control-plane-close'); + ).toContain('workspace-mobile-control-plane-close'); const workspaceRoot = process.cwd().endsWith('/libs/workspace-react') ? resolve(process.cwd(), '../..') @@ -786,36 +788,36 @@ describe('MobileNavOverlay', () => { 'utf8' ); expect(css).toMatch( - /@media \(pointer: coarse\)[\s\S]*\.cockpit-mobile-navigation-trigger[\s\S]*\.cockpit-mobile-control-plane-close/ + /@media \(pointer: coarse\)[\s\S]*\.workspace-mobile-navigation-trigger[\s\S]*\.workspace-mobile-control-plane-close/ ); expect(css).toMatch( - /\.cockpit-mobile-navigation-trigger[\s\S]*min-width:\s*44px[\s\S]*min-height:\s*44px/ + /\.workspace-mobile-navigation-trigger[\s\S]*min-width:\s*44px[\s\S]*min-height:\s*44px/ ); expect(css).toMatch( - /\.cockpit-mobile-navigation-trigger\s*\{[^}]*flex:\s*none;[^}]*min-width:\s*44px;[^}]*min-height:\s*44px;/ + /\.workspace-mobile-navigation-trigger\s*\{[^}]*flex:\s*none;[^}]*min-width:\s*44px;[^}]*min-height:\s*44px;/ ); expect(css).toMatch( - /\.cockpit-mobile-control-plane-close[\s\S]*min-width:\s*44px[\s\S]*min-height:\s*44px/ + /\.workspace-mobile-control-plane-close[\s\S]*min-width:\s*44px[\s\S]*min-height:\s*44px/ ); - expect(css).toContain('.cockpit-mobile-navigation-trigger:focus-visible'); - expect(css).toContain('.cockpit-tablet-context-trigger:focus-visible'); + expect(css).toContain('.workspace-mobile-navigation-trigger:focus-visible'); + expect(css).toContain('.workspace-tablet-context-trigger:focus-visible'); expect(css).toMatch( - /\.cockpit-mobile-control-plane\s*\{[^}]*position:\s*fixed;[^}]*z-index:\s*50;/ + /\.workspace-mobile-control-plane\s*\{[^}]*position:\s*fixed;[^}]*z-index:\s*50;/ ); expect(css).toMatch( - /\.cockpit-mobile-control-plane\[data-variant=["']mobile["']\]\s*\{[^}]*inset:\s*0;/ + /\.workspace-mobile-control-plane\[data-variant=["']mobile["']\]\s*\{[^}]*inset:\s*0;/ ); expect(css).toMatch( - /@media \(forced-colors: active\)[\s\S]*\.cockpit-tablet-context-trigger[\s\S]*border:\s*1px solid CanvasText/ + /@media \(forced-colors: active\)[\s\S]*\.workspace-tablet-context-trigger[\s\S]*border:\s*1px solid CanvasText/ ); expect(css).toMatch( - /@media \(pointer: coarse\)[\s\S]*\.cockpit-tablet-context-trigger[\s\S]*min-width:\s*44px[\s\S]*min-height:\s*44px/ + /@media \(pointer: coarse\)[\s\S]*\.workspace-tablet-context-trigger[\s\S]*min-width:\s*44px[\s\S]*min-height:\s*44px/ ); expect(css).toMatch( - /@media \(prefers-reduced-motion: reduce\)[\s\S]*\.cockpit-mobile-control-plane[\s\S]*transition:\s*none\s*!important/ + /@media \(prefers-reduced-motion: reduce\)[\s\S]*\.workspace-mobile-control-plane[\s\S]*transition:\s*none\s*!important/ ); expect(css).toMatch( - /@media \(prefers-reduced-motion: reduce\)[\s\S]*\.cockpit-runtime-status-loader[\s\S]*animation:\s*none\s*!important/ + /@media \(prefers-reduced-motion: reduce\)[\s\S]*\.workspace-runtime-status-loader[\s\S]*animation:\s*none\s*!important/ ); }); }); diff --git a/libs/workspace-react/src/lib/components/mobile-nav-overlay.tsx b/libs/workspace-react/src/lib/components/mobile-nav-overlay.tsx index 14c36fa79..f83408b8c 100644 --- a/libs/workspace-react/src/lib/components/mobile-nav-overlay.tsx +++ b/libs/workspace-react/src/lib/components/mobile-nav-overlay.tsx @@ -369,22 +369,22 @@ export function MobileNavOverlay({ data-variant={variant} className={ variant === 'tablet' - ? 'cockpit-mobile-control-plane cockpit-tablet-context-surface fixed z-50' - : 'cockpit-mobile-control-plane fixed inset-0 z-50' + ? 'workspace-mobile-control-plane workspace-tablet-context-surface fixed z-50' + : 'workspace-mobile-control-plane fixed inset-0 z-50' } onClickCapture={interceptWorkspaceNavigation} onMouseDown={(event) => { if (event.target === event.currentTarget) requestClose('trigger'); }} > -
        -
        +
        +
        {title} diff --git a/libs/workspace-react/src/lib/components/narrative-docs/narrative-docs.tsx b/libs/workspace-react/src/lib/components/narrative-docs/narrative-docs.tsx index 00006e35d..4be0ae3fe 100644 --- a/libs/workspace-react/src/lib/components/narrative-docs/narrative-docs.tsx +++ b/libs/workspace-react/src/lib/components/narrative-docs/narrative-docs.tsx @@ -60,7 +60,7 @@ export function NarrativeDocs({
        ))} diff --git a/libs/workspace-react/src/lib/components/sidebar/cockpit-sidebar.tsx b/libs/workspace-react/src/lib/components/sidebar/cockpit-sidebar.tsx index 495705db6..2a3e1e1b1 100644 --- a/libs/workspace-react/src/lib/components/sidebar/cockpit-sidebar.tsx +++ b/libs/workspace-react/src/lib/components/sidebar/cockpit-sidebar.tsx @@ -31,9 +31,9 @@ export function CockpitSidebar({ const product = PRODUCT_LABELS[entry.product] ?? entry.product; return ( -
        +
        -
        +
        {product} {entry.section diff --git a/libs/workspace-react/src/lib/components/sidebar/navigation-groups.spec.tsx b/libs/workspace-react/src/lib/components/sidebar/navigation-groups.spec.tsx index 40d39b809..c915e77a7 100644 --- a/libs/workspace-react/src/lib/components/sidebar/navigation-groups.spec.tsx +++ b/libs/workspace-react/src/lib/components/sidebar/navigation-groups.spec.tsx @@ -49,11 +49,11 @@ describe('NavigationGroups capability link instrumentation', () => { }); it('uses complete rounded sidebar states without a left marker', () => { - const item = declarationsFor(workspaceCss, '.cockpit-nav-item'); - const hover = declarationsFor(workspaceCss, '.cockpit-nav-item:hover'); + const item = declarationsFor(workspaceCss, '.workspace-nav-item'); + const hover = declarationsFor(workspaceCss, '.workspace-nav-item:hover'); const active = declarationsFor( workspaceCss, - ".cockpit-nav-item[aria-current='page']" + ".workspace-nav-item[aria-current='page']" ); expect(item).toMatch(/border-radius:\s*(?:6px|7px)/); @@ -175,23 +175,23 @@ describe('NavigationGroups capability link instrumentation', () => { const template = document.createElement('template'); template.innerHTML = html; const chevrons = template.content.querySelectorAll( - '.cockpit-nav-caret > svg.lucide-chevron-right' + '.workspace-nav-caret > svg.lucide-chevron-right' ); - expect(html).toContain('cockpit-nav-group-label'); + expect(html).toContain('workspace-nav-group-label'); expect(chevrons.length).toBeGreaterThan(0); for (const chevron of chevrons) { expect(chevron.getAttribute('width')).toBe('15'); expect(chevron.getAttribute('height')).toBe('15'); expect(chevron.getAttribute('stroke-width')).toBe('2'); expect(chevron.parentElement?.className).toContain( - 'cockpit-nav-caret--open' + 'workspace-nav-caret--open' ); } - expect(declarationsFor(workspaceCss, '.cockpit-nav-caret')).toMatch( + expect(declarationsFor(workspaceCss, '.workspace-nav-caret')).toMatch( /transition:[^;]*transform\s+150ms\s+ease/ ); - expect(declarationsFor(workspaceCss, '.cockpit-nav-caret--open')).toMatch( + expect(declarationsFor(workspaceCss, '.workspace-nav-caret--open')).toMatch( /transform:\s*rotate\(90deg\)/ ); expect(html).not.toContain('text-transform:uppercase'); @@ -218,7 +218,9 @@ describe('NavigationGroups capability link instrumentation', () => { const landmarks = container.querySelectorAll('nav'); expect(landmarks).toHaveLength(1); - expect(landmarks[0]?.getAttribute('aria-label')).toBe('Cockpit navigation'); + expect(landmarks[0]?.getAttribute('aria-label')).toBe( + 'Documentation navigation' + ); const disclosure = container.querySelector( 'button[aria-expanded]' ); diff --git a/libs/workspace-react/src/lib/components/sidebar/navigation-groups.tsx b/libs/workspace-react/src/lib/components/sidebar/navigation-groups.tsx index 7eb12f0ef..9ab3b8a68 100644 --- a/libs/workspace-react/src/lib/components/sidebar/navigation-groups.tsx +++ b/libs/workspace-react/src/lib/components/sidebar/navigation-groups.tsx @@ -61,10 +61,10 @@ function ProductGroup({ cursor: 'pointer', }} > - {label} + {label}