diff --git a/.gitignore b/.gitignore index 3162a1552..f2e23c963 100644 --- a/.gitignore +++ b/.gitignore @@ -49,4 +49,5 @@ libs/licensing/src/lib/license-public-key.generated.ts # superpowers brainstorming output .superpowers/ -tools/posthog/tsconfig.tsbuildinfo +# TypeScript incremental build caches (any project) +*.tsbuildinfo diff --git a/apps/cockpit/src/app/cockpit.css b/apps/cockpit/src/app/cockpit.css index 56e71943e..6ec4123c6 100644 --- a/apps/cockpit/src/app/cockpit.css +++ b/apps/cockpit/src/app/cockpit.css @@ -109,31 +109,30 @@ pre.shiki { align-items: center; gap: 0.5rem; padding: 0.4rem 0.75rem; - border-bottom: 1px solid rgba(138, 170, 214, 0.12); - background: var(--ds-surface); + border-bottom: 1px solid var(--ds-border); + background: var(--ds-surface-tinted); font-size: 0.7rem; } -.doc-codeblock__file { color: #a9b1d6; font-family: var(--font-mono); } +.doc-codeblock__file { color: var(--ds-text-secondary); font-family: var(--font-mono), "JetBrains Mono", monospace; } .doc-codeblock__lang { padding: 0.1rem 0.35rem; border-radius: 0.2rem; - background: var(--ds-accent-border); - color: var(--ds-accent-light); + background: var(--ds-accent-surface); + color: var(--ds-accent); font-size: 0.6rem; + font-family: var(--font-mono), "JetBrains Mono", monospace; } .doc-codeblock__copy { margin-left: auto; padding: 0.1rem 0.5rem; - border: 1px solid rgba(255, 255, 255, 0.15); + border: 1px solid var(--ds-border); border-radius: 0.25rem; background: transparent; - color: #a9b1d6; - font-size: 0.65rem; + color: var(--ds-text-muted); cursor: pointer; } -.doc-codeblock__copy:hover { color: #e0e0e0; } +.doc-codeblock__copy:hover { color: var(--ds-text-primary); border-color: var(--ds-border-strong); } .doc-codeblock pre.shiki { margin: 0; border-radius: 0; border: none; overflow-x: auto; } -.code-mode-block pre.shiki { margin: 0; border-radius: 0; border: none; overflow-x: auto; } .doc-prompt { background: rgba(168, 85, 247, 0.04); @@ -206,12 +205,29 @@ pre.shiki { font-size: 0.75rem; } -/* Narrative docs heading alignment with website */ -.docs-article h1, -.docs-article h2, -.docs-article h3 { - font-family: var(--ds-font-serif); -} -.docs-article h1 { font-size: 1.875rem; } -.docs-article h2 { font-size: 1.5rem; } -.docs-article h3 { font-size: 1.25rem; } +/* Shared prose layer β€” docs + api */ +.cockpit-prose { + max-width: 42rem; + font-size: 0.9rem; + line-height: 1.7; + color: var(--ds-text-secondary); +} +.cockpit-prose h1, .cockpit-prose h2, .cockpit-prose h3 { + font-family: var(--font-garamond), var(--ds-font-serif); + color: var(--ds-text-primary); + letter-spacing: -0.01em; +} +.cockpit-prose h1 { font-size: 1.875rem; line-height: 1.1; margin: 0 0 0.5rem; padding-bottom: 0.75rem; border-bottom: 1px solid var(--ds-accent-border); } +.cockpit-prose h2 { font-size: 1.5rem; margin: 2.25rem 0 0.75rem; } +.cockpit-prose h3 { font-size: 1.25rem; margin: 1.5rem 0 0.5rem; } +.cockpit-prose p { margin: 0 0 0.75rem; } +.cockpit-prose ul { margin: 0 0 0.75rem; padding-left: 1.25rem; list-style: disc; } +.cockpit-prose li { margin-bottom: 0.25rem; } +.cockpit-prose a { color: var(--ds-accent); text-decoration: none; } +.cockpit-prose a:hover { text-decoration: underline; } +.cockpit-prose code { color: var(--ds-accent); background: var(--ds-accent-surface); padding: 0.1rem 0.3rem; border-radius: 0.25rem; font-size: 0.85em; font-family: var(--font-mono), "JetBrains Mono", monospace; } +.cockpit-prose strong { color: var(--ds-text-primary); font-weight: 600; } + +.cockpit-prose table.params { border-collapse: collapse; margin: 0.5rem 0; } +.cockpit-prose table.params th { font-family: var(--font-mono), monospace; font-size: 0.6rem; letter-spacing: 0.06em; text-transform: uppercase; padding-bottom: 0.5rem; border-bottom: 1px solid var(--ds-border); } +.cockpit-prose table.params td { padding: 0.5rem 0.75rem 0.5rem 0; border-bottom: 1px solid var(--ds-border); } diff --git a/apps/cockpit/src/app/layout.tsx b/apps/cockpit/src/app/layout.tsx index a7df24515..c17599eed 100644 --- a/apps/cockpit/src/app/layout.tsx +++ b/apps/cockpit/src/app/layout.tsx @@ -4,6 +4,11 @@ import { cssVars, ThemeProvider } from '@threadplane/ui-react'; import type { Theme } from '@threadplane/design-tokens'; import { AnalyticsBootstrap } from '../components/analytics-bootstrap'; import './cockpit.css'; +import { EB_Garamond, Inter, JetBrains_Mono } from 'next/font/google'; + +const inter = Inter({ subsets: ['latin'], variable: '--font-inter', display: 'swap' }); +const mono = JetBrains_Mono({ subsets: ['latin'], variable: '--font-mono', display: 'swap' }); +const garamond = EB_Garamond({ subsets: ['latin'], weight: ['400', '500', '600'], variable: '--font-garamond', display: 'swap' }); export const metadata = { title: 'Cockpit β€” Threadplane', @@ -31,7 +36,12 @@ export default async function RootLayout({ children }: RootLayoutProps) { const theme: Theme = cookieValue === 'light' ? 'light' : 'dark'; return ( - + { expect(html).toContain('stream'); expect(html).toContain('prompt'); expect(html).toContain('The user message'); + expect(html).toContain(' 0 ? (
-
+
Parameters
-
- {section.params.map((param) => ( -
- - {param.name} - - {renderInlineCode(param.description)} -
- ))} -
+ + + + + + + + + {section.params.map((param) => ( + + + + + ))} + +
ParameterDescription
+ + {param.name} + + + {renderInlineCode(param.description)} +
) : null} @@ -127,34 +130,36 @@ export function ApiMode({ docSections, hasCodeFiles = false }: ApiModeProps) { const pySections = docSections.filter((s) => s.language === 'python'); return ( -
- {tsSections.length > 0 ? ( -
-

- {LANGUAGE_LABELS[tsSections[0]?.language] ?? 'TypeScript'} -

- {tsSections.map((section) => ( - - ))} -
- ) : null} +
+
+ {tsSections.length > 0 ? ( +
+

+ {LANGUAGE_LABELS[tsSections[0]?.language] ?? 'TypeScript'} +

+ {tsSections.map((section) => ( + + ))} +
+ ) : null} - {pySections.length > 0 ? ( -
-

- {LANGUAGE_LABELS[pySections[0]?.language] ?? 'Python'} -

- {pySections.map((section) => ( - - ))} -
- ) : null} + {pySections.length > 0 ? ( +
+

+ {LANGUAGE_LABELS[pySections[0]?.language] ?? 'Python'} +

+ {pySections.map((section) => ( + + ))} +
+ ) : null} +
); } diff --git a/apps/cockpit/src/components/branding/logo.spec.tsx b/apps/cockpit/src/components/branding/logo.spec.tsx new file mode 100644 index 000000000..535b87c88 --- /dev/null +++ b/apps/cockpit/src/components/branding/logo.spec.tsx @@ -0,0 +1,21 @@ +import React from 'react'; +import { renderToStaticMarkup } from 'react-dom/server'; +import { describe, expect, it } from 'vitest'; +import { Logo } from './logo'; + +describe('Logo', () => { + it('renders the Threadplane wordmark', () => { + const html = renderToStaticMarkup(); + expect(html).toContain('Threadplane'); + }); + + it('renders the cockpit qualifier', () => { + const html = renderToStaticMarkup(); + expect(html).toContain('cockpit'); + }); + + it('exposes a stable data-ui selector', () => { + const html = renderToStaticMarkup(); + expect(html).toContain('data-ui="cockpit-logo"'); + }); +}); diff --git a/apps/cockpit/src/components/branding/logo.tsx b/apps/cockpit/src/components/branding/logo.tsx new file mode 100644 index 000000000..64ff6dd23 --- /dev/null +++ b/apps/cockpit/src/components/branding/logo.tsx @@ -0,0 +1,21 @@ +import React from 'react'; +import type { HTMLAttributes } from 'react'; + +export function Logo({ className, style, ...rest }: HTMLAttributes) { + return ( + + + + Threadplane + + + cockpit + + + ); +} diff --git a/apps/cockpit/src/components/cockpit-shell.tsx b/apps/cockpit/src/components/cockpit-shell.tsx index 55f00f21b..2c6956dca 100644 --- a/apps/cockpit/src/components/cockpit-shell.tsx +++ b/apps/cockpit/src/components/cockpit-shell.tsx @@ -94,8 +94,6 @@ export function CockpitShell({

{contextLabel}

- | -

{entryTitle}

{ }); expect(container.querySelector('.shiki')).not.toBeNull(); + const fileLabel = container.querySelector('.doc-codeblock__file'); + expect(fileLabel?.textContent).toBe('page.tsx'); expect(container.textContent).toContain('export default function Page() {}'); const tabs = Array.from(container.querySelectorAll('[role="tab"]')); diff --git a/apps/cockpit/src/components/code-mode/code-mode.tsx b/apps/cockpit/src/components/code-mode/code-mode.tsx index 37a1a0e82..ae52163b6 100644 --- a/apps/cockpit/src/components/code-mode/code-mode.tsx +++ b/apps/cockpit/src/components/code-mode/code-mode.tsx @@ -28,43 +28,23 @@ function CodeFileContent({ return

No source available for {getTabLabel(path)}

; } + const label = getTabLabel(path); + const dotIdx = label.lastIndexOf('.'); + const ext = dotIdx > 0 ? label.slice(dotIdx + 1).toUpperCase() : ''; + return ( -
-
- {path} +
+
+ {label} + {ext ? {ext} : null}
@@ -108,7 +88,7 @@ export function CodeMode({ entryTitle, codeAssetPaths, backendAssetPaths, codeFi {[...codeAssetPaths, ...backendAssetPaths].map((path) => ( - + ))} diff --git a/apps/cockpit/src/components/narrative-docs/narrative-docs.tsx b/apps/cockpit/src/components/narrative-docs/narrative-docs.tsx index 012c1a690..625d0db77 100644 --- a/apps/cockpit/src/components/narrative-docs/narrative-docs.tsx +++ b/apps/cockpit/src/components/narrative-docs/narrative-docs.tsx @@ -55,16 +55,7 @@ export function NarrativeDocs({ narrativeDocs, capability }: NarrativeDocsProps)
))} diff --git a/apps/cockpit/src/components/sidebar/cockpit-sidebar.tsx b/apps/cockpit/src/components/sidebar/cockpit-sidebar.tsx index 6530d4af3..89a8d077a 100644 --- a/apps/cockpit/src/components/sidebar/cockpit-sidebar.tsx +++ b/apps/cockpit/src/components/sidebar/cockpit-sidebar.tsx @@ -4,6 +4,7 @@ import type { CockpitManifestEntry, } from '@threadplane/cockpit-registry'; import type { NavigationProduct } from '../../lib/route-resolution'; +import { Logo } from '../branding/logo'; import { LanguagePicker } from './language-picker'; import { NavigationGroups } from './navigation-groups'; @@ -29,7 +30,7 @@ export function CockpitSidebar({ }} >
-

Cockpit

+
diff --git a/docs/superpowers/plans/2026-05-29-cockpit-redesign.md b/docs/superpowers/plans/2026-05-29-cockpit-redesign.md new file mode 100644 index 000000000..408918629 --- /dev/null +++ b/docs/superpowers/plans/2026-05-29-cockpit-redesign.md @@ -0,0 +1,594 @@ +# Cockpit Redesign Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Reskin the cockpit app as a branded dark console whose palette and surfaces align with the `@threadplane/chat` library, and clean up the Code / Docs / API content modes to match the marketing website. + +**Architecture:** Pure `apps/cockpit` (Next.js, Tailwind v4) change. Consume the existing chat-aligned dark tokens from `@threadplane/design-tokens`, remove hardcoded color literals, add a brand logo lockup, load the brand webfonts, and consolidate the two divergent code-block treatments onto the existing `.doc-codeblock` CSS classes (made theme-aware). Light mode and the `ThemedFrame`β†’iframe theme handshake are preserved unchanged. + +**Tech Stack:** Next.js 16, React, Tailwind v4 (CSS-based, no config file), Radix Tabs, Shiki (Tokyo Night), Vitest + jsdom (tests use `createRoot`/`act`, not testing-library), `next/font/google`. + +**Spec:** `docs/superpowers/specs/2026-05-29-cockpit-redesign-design.md` + +**Conventions:** +- Tests follow the existing harness style (see `apps/cockpit/src/components/code-mode/code-mode.spec.tsx`): `createRoot`, `act`, query the DOM, no `@testing-library`. +- Run a single test file with: `npx nx test cockpit -- ` (or `npx vitest run ` from repo root β€” confirm which the repo uses by checking `apps/cockpit/project.json` `test` target). +- Commit after each task. + +--- + +## File map + +| File | Change | +|------|--------| +| `apps/cockpit/src/app/layout.tsx` | Load Inter / JetBrains Mono / EB Garamond via `next/font`, expose `--font-inter` `--font-mono` `--font-garamond` on ``. | +| `apps/cockpit/src/components/branding/logo.tsx` | **NEW** β€” Threadplane logo lockup (πŸ›©οΈ + serif wordmark + mono `cockpit`). | +| `apps/cockpit/src/components/branding/logo.spec.tsx` | **NEW** β€” test. | +| `apps/cockpit/src/components/sidebar/cockpit-sidebar.tsx` | Replace bare "Cockpit" text with ``. | +| `apps/cockpit/src/components/cockpit-shell.tsx` | Remove the capability-title `

` and `|` divider from the header. | +| `apps/cockpit/src/app/cockpit.css` | Make `.doc-codeblock` header theme-aware (tokens, not hardcoded); ensure code body `overflow-x`; add consolidated `.cockpit-prose` class. | +| `apps/cockpit/src/components/code-mode/code-mode.tsx` | Render the shared `.doc-codeblock` markup with a **short filename** (not full path); drop inline hardcoded colors. | +| `apps/cockpit/src/components/code-mode/code-mode.spec.tsx` | Update header assertion to short filename. | +| `apps/cockpit/src/components/narrative-docs/narrative-docs.tsx` | Replace the long `[&_h1]:…` chains with `className="cockpit-prose"`. | +| `apps/cockpit/src/components/api-mode/api-mode.tsx` | Wrap in `.cockpit-prose`; render params as a responsive ``; signatures in shared code style; drop hardcoded colors. | +| `apps/cockpit/src/components/api-mode/api-mode.spec.tsx` | Add assertion for table markup. | + +--- + +## Task 1: Load brand webfonts + +**Files:** +- Modify: `apps/cockpit/src/app/layout.tsx` + +The cockpit currently loads none of the brand fonts, so mono labels fall back to system mono and serif docs headings fall back to Georgia. Mirror `apps/website/src/app/layout.tsx`. + +- [ ] **Step 1: Add the font imports and variables** + +At the top of `apps/cockpit/src/app/layout.tsx`, after the existing imports add: + +```tsx +import { EB_Garamond, Inter, JetBrains_Mono } from 'next/font/google'; + +const inter = Inter({ subsets: ['latin'], variable: '--font-inter', display: 'swap' }); +const mono = JetBrains_Mono({ subsets: ['latin'], variable: '--font-mono', display: 'swap' }); +const garamond = EB_Garamond({ subsets: ['latin'], weight: ['400', '500', '600'], variable: '--font-garamond', display: 'swap' }); +``` + +- [ ] **Step 2: Apply the variables to ``** + +Change the `` open tag to include the font variable classes alongside the existing `data-theme` and `style`: + +```tsx + +``` + +- [ ] **Step 3: Verify existing tests still pass** + +Run: `npx nx test cockpit` +Expected: PASS (no behavioral change; layout has no spec that asserts fonts). + +- [ ] **Step 4: Browser verify** + +Run `npx nx serve cockpit`, open `http://localhost:4201/deep-agents/core-capabilities/planning/overview/python`, switch to Docs mode. Sidebar section labels render in JetBrains Mono; docs `h1/h2/h3` render in EB Garamond (serif), not Georgia. + +- [ ] **Step 5: Commit** + +```bash +git add apps/cockpit/src/app/layout.tsx +git commit -m "feat(cockpit): load brand webfonts (Inter, JetBrains Mono, EB Garamond)" +``` + +--- + +## Task 2: Logo lockup component + +**Files:** +- Create: `apps/cockpit/src/components/branding/logo.tsx` +- Test: `apps/cockpit/src/components/branding/logo.spec.tsx` + +Mirrors `apps/website/src/components/ui/LogoMark.tsx` but uses theme tokens (`var(--ds-text-primary)`) so it adapts to light/dark, and adds a mono `cockpit` qualifier. + +- [ ] **Step 1: Write the failing test** + +```tsx +/** @vitest-environment jsdom */ +import React from 'react'; +import { act } from 'react'; +import { createRoot } from 'react-dom/client'; +import { afterEach, describe, expect, it } from 'vitest'; +import { Logo } from './logo'; + +describe('Logo', () => { + let container: HTMLDivElement | undefined; + let root: ReturnType | undefined; + + afterEach(() => { + act(() => { root?.unmount(); }); + container?.remove(); + }); + + it('renders the Threadplane wordmark and the cockpit qualifier', () => { + container = document.createElement('div'); + document.body.appendChild(container); + root = createRoot(container); + act(() => { root!.render(); }); + + expect(container.textContent).toContain('Threadplane'); + expect(container.textContent).toContain('cockpit'); + }); +}); +``` + +- [ ] **Step 2: Run test to verify it fails** + +Run: `npx nx test cockpit -- src/components/branding/logo.spec.tsx` +Expected: FAIL β€” cannot resolve `./logo`. + +- [ ] **Step 3: Write the component** + +```tsx +import type { HTMLAttributes } from 'react'; + +export function Logo({ className, style, ...rest }: HTMLAttributes) { + return ( + + + + Threadplane + + + cockpit + + + ); +} +``` + +- [ ] **Step 4: Run test to verify it passes** + +Run: `npx nx test cockpit -- src/components/branding/logo.spec.tsx` +Expected: PASS. + +- [ ] **Step 5: Commit** + +```bash +git add apps/cockpit/src/components/branding/logo.tsx apps/cockpit/src/components/branding/logo.spec.tsx +git commit -m "feat(cockpit): add Threadplane logo lockup component" +``` + +--- + +## Task 3: Use the logo in the sidebar + +**Files:** +- Modify: `apps/cockpit/src/components/sidebar/cockpit-sidebar.tsx` + +- [ ] **Step 1: Import and swap the header text** + +Add `import { Logo } from '../branding/logo';` to the imports. Replace the header `

…Cockpit…

` (lines ~31-34) so the header becomes: + +```tsx +
+ + +
+``` + +- [ ] **Step 2: Run sidebar tests** + +Run: `npx nx test cockpit -- src/components/sidebar/cockpit-sidebar.spec.tsx` +Expected: PASS. If a test asserts the literal text `Cockpit` in the header, update it to assert `Threadplane`. + +- [ ] **Step 3: Browser verify** + +Reload the cockpit; the sidebar top shows the πŸ›©οΈ + "Threadplane" + mono "cockpit" lockup in both light and dark. + +- [ ] **Step 4: Commit** + +```bash +git add apps/cockpit/src/components/sidebar/cockpit-sidebar.tsx apps/cockpit/src/components/sidebar/cockpit-sidebar.spec.tsx +git commit -m "feat(cockpit): show Threadplane logo lockup in sidebar" +``` + +--- + +## Task 4: Remove the capability title from the header + +**Files:** +- Modify: `apps/cockpit/src/components/cockpit-shell.tsx` + +The header keeps the mono breadcrumb (`contextLabel`) and the mode switcher; the `entryTitle` prop stays (Run mode uses it for the iframe title, Code mode for fallbacks) but is no longer shown in the header. + +- [ ] **Step 1: Remove the title markup** + +In `cockpit-shell.tsx`, inside the header `
`, delete these two lines: + +```tsx +| +

{entryTitle}

+``` + +Leave the `MenuIcon` button and the `contextLabel` `

` in place. + +- [ ] **Step 2: Run pane-rendering tests** + +Run: `npx nx test cockpit -- src/components/pane-rendering.spec.tsx` +Expected: PASS. (It passes `entryTitle` as a prop but does not assert the header `

`; if any assertion checks the title text in the header, remove that assertion.) + +- [ ] **Step 3: Browser verify** + +Reload; the header shows only the breadcrumb on the left and Run/Code/Docs/API on the right β€” no large title. + +- [ ] **Step 4: Commit** + +```bash +git add apps/cockpit/src/components/cockpit-shell.tsx +git commit -m "refactor(cockpit): drop redundant capability title from header" +``` + +--- + +## Task 5: Make the shared code block theme-aware + +**Files:** +- Modify: `apps/cockpit/src/app/cockpit.css` + +`.doc-codeblock` is the canonical clean code block (filename + lang + Copy). Its header currently uses hardcoded colors that don't adapt to light mode. Move them to tokens and ensure the code body scrolls horizontally on its own. + +- [ ] **Step 1: Replace the hardcoded header/copy colors** + +In `cockpit.css`, update the `.doc-codeblock__*` rules so they read: + +```css +.doc-codeblock__header { + display: flex; + align-items: center; + gap: 0.5rem; + padding: 0.4rem 0.75rem; + border-bottom: 1px solid var(--ds-border); + background: var(--ds-surface-tinted); + font-size: 0.7rem; +} +.doc-codeblock__file { color: var(--ds-text-secondary); font-family: var(--font-mono), "JetBrains Mono", monospace; } +.doc-codeblock__lang { + padding: 0.1rem 0.35rem; + border-radius: 0.2rem; + background: var(--ds-accent-surface); + color: var(--ds-accent); + font-size: 0.6rem; + font-family: var(--font-mono), "JetBrains Mono", monospace; +} +.doc-codeblock__copy { + margin-left: auto; + padding: 0.1rem 0.5rem; + border: 1px solid var(--ds-border); + border-radius: 0.25rem; + background: transparent; + color: var(--ds-text-muted); + cursor: pointer; +} +.doc-codeblock__copy:hover { color: var(--ds-text-primary); border-color: var(--ds-border-strong); } +``` + +Also change the existing `.doc-codeblock__file` line that read `color: #a9b1d6` (β‰ˆ line 116) β€” it is replaced by the rule above; delete the stale duplicate. + +- [ ] **Step 2: Ensure the code body scrolls horizontally inside the block** + +Confirm the `pre.shiki` rule keeps `overflow-x: auto` (it does, ~line 7). Add, after it: + +```css +.doc-codeblock pre.shiki { margin: 0; border-radius: 0; } +.doc-codeblock { max-width: 100%; } +``` + +- [ ] **Step 3: Run markdown renderer tests** + +Run: `npx nx test cockpit -- src/lib/render-markdown.spec.ts` +Expected: PASS (class names unchanged; only CSS values changed). + +- [ ] **Step 4: Browser verify (both themes)** + +In Docs mode, a fenced code block shows a token-colored header that adapts when you toggle the theme; long lines scroll inside the block, not the page. + +- [ ] **Step 5: Commit** + +```bash +git add apps/cockpit/src/app/cockpit.css +git commit -m "fix(cockpit): make shared code block theme-aware and contain overflow" +``` + +--- + +## Task 6: Code mode adopts the shared code block + +**Files:** +- Modify: `apps/cockpit/src/components/code-mode/code-mode.tsx` +- Modify: `apps/cockpit/src/components/code-mode/code-mode.spec.tsx` + +Replace the bespoke `.code-mode-block` (full path header, hardcoded `#a9b1d6`/`rgba(26,27,38,.95)`/`#4A527A`) with the shared `.doc-codeblock` markup, showing the **short filename**. + +- [ ] **Step 1: Update the failing test first** + +In `code-mode.spec.tsx`, the first test currently only checks `.shiki` and tab labels. Add an assertion that the block header shows the short filename (not the full path): + +```tsx +const fileLabel = container.querySelector('.doc-codeblock__file'); +expect(fileLabel?.textContent).toBe('page.tsx'); +``` + +Add this right after the `expect(container.querySelector('.shiki')).not.toBeNull();` line in the first test. + +- [ ] **Step 2: Run test to verify it fails** + +Run: `npx nx test cockpit -- src/components/code-mode/code-mode.spec.tsx` +Expected: FAIL β€” `.doc-codeblock__file` not found (current markup uses `.code-mode-block` with the full path). + +- [ ] **Step 3: Rewrite `CodeFileContent` to use the shared markup** + +Replace the `CodeFileContent` function body in `code-mode.tsx` with: + +```tsx +function CodeFileContent({ + path, + content, + capability, +}: { + path: string; + content: string | undefined; + capability?: string; +}) { + if (!content) { + return

No source available for {getTabLabel(path)}

; + } + + const ext = (getTabLabel(path).split('.').pop() ?? '').toUpperCase(); + + return ( +
+
+ {getTabLabel(path)} + {ext ? {ext} : null} + +
+
+
+ ); +} +``` + +(Note: the `data-code-path` wrapper is kept so the existing Copy query selector still works.) + +- [ ] **Step 4: Run test to verify it passes** + +Run: `npx nx test cockpit -- src/components/code-mode/code-mode.spec.tsx` +Expected: PASS (short filename rendered; the Copy-tracking test still passes β€” `aria-label` and `track` call are unchanged). + +- [ ] **Step 5: Browser verify** + +Code mode shows a clean header (short filename + uppercase ext chip + legible Copy); long lines scroll inside the block; toggling the theme adapts the header. + +- [ ] **Step 6: Commit** + +```bash +git add apps/cockpit/src/components/code-mode/code-mode.tsx apps/cockpit/src/components/code-mode/code-mode.spec.tsx +git commit -m "refactor(cockpit): code mode uses shared theme-aware code block" +``` + +--- + +## Task 7: Consolidate docs prose + +**Files:** +- Modify: `apps/cockpit/src/app/cockpit.css` +- Modify: `apps/cockpit/src/components/narrative-docs/narrative-docs.tsx` + +Replace the long inline `[&_h1]:…` Tailwind chains with a single `.cockpit-prose` class that both Docs and API will use. + +- [ ] **Step 1: Add the `.cockpit-prose` class to `cockpit.css`** + +Append (and fold in the existing `.docs-article h1/h2/h3 { font-family: var(--ds-font-serif) }` rule, replacing it): + +```css +/* Shared prose layer β€” docs + api */ +.cockpit-prose { + max-width: 42rem; + font-size: 0.9rem; + line-height: 1.7; + color: var(--ds-text-secondary); +} +.cockpit-prose h1, .cockpit-prose h2, .cockpit-prose h3 { + font-family: var(--font-garamond), var(--ds-font-serif); + color: var(--ds-text-primary); + letter-spacing: -0.01em; +} +.cockpit-prose h1 { font-size: 1.875rem; line-height: 1.1; margin: 0 0 0.5rem; padding-bottom: 0.75rem; border-bottom: 1px solid var(--ds-accent-border); } +.cockpit-prose h2 { font-size: 1.5rem; margin: 2.25rem 0 0.75rem; } +.cockpit-prose h3 { font-size: 1.25rem; margin: 1.5rem 0 0.5rem; } +.cockpit-prose p { margin: 0 0 0.75rem; } +.cockpit-prose ul { margin: 0 0 0.75rem; padding-left: 1.25rem; list-style: disc; } +.cockpit-prose li { margin-bottom: 0.25rem; } +.cockpit-prose a { color: var(--ds-accent); text-decoration: none; } +.cockpit-prose a:hover { text-decoration: underline; } +.cockpit-prose code { color: var(--ds-accent); background: var(--ds-accent-surface); padding: 0.1rem 0.3rem; border-radius: 0.25rem; font-size: 0.85em; font-family: var(--font-mono), "JetBrains Mono", monospace; } +.cockpit-prose strong { color: var(--ds-text-primary); font-weight: 600; } +``` + +- [ ] **Step 2: Use the class in `narrative-docs.tsx`** + +Replace the long `className="docs-article …[&_h1]:…"` value on the `
` with just: + +```tsx +className="cockpit-prose" +``` + +Keep `docs-article` too if any other CSS targets it: `className="docs-article cockpit-prose"`. Keep the `onClick` delegation and `dangerouslySetInnerHTML` unchanged. + +- [ ] **Step 3: Run docs tests** + +Run: `npx nx test cockpit -- src/components/narrative-docs/narrative-docs.spec.tsx` +Expected: PASS (markup/content unchanged; only class names). + +- [ ] **Step 4: Browser verify (responsive + both themes)** + +Docs mode renders a readable prose column with serif headings, the shared code block, and callouts. Resize to a narrow viewport: single column, no horizontal overflow. + +- [ ] **Step 5: Commit** + +```bash +git add apps/cockpit/src/app/cockpit.css apps/cockpit/src/components/narrative-docs/narrative-docs.tsx +git commit -m "refactor(cockpit): consolidate docs styling into shared .cockpit-prose" +``` + +--- + +## Task 8: API mode β€” prose container + responsive param table + +**Files:** +- Modify: `apps/cockpit/src/components/api-mode/api-mode.tsx` +- Modify: `apps/cockpit/src/components/api-mode/api-mode.spec.tsx` + +Wrap sections in `.cockpit-prose`, render signatures in the shared code style, and turn the cramped flex param rows into a responsive `

`. The existing spec asserts on text content (names/descriptions), which the table preserves. + +- [ ] **Step 1: Add a table-markup assertion to the spec** + +In `api-mode.spec.tsx`, after the existing `expect(html).toContain('The user message');` add: + +```tsx +expect(html).toContain('` in current markup. + +- [ ] **Step 3: Refactor `DocArticle` to use a table and tokens** + +Replace the params block inside `DocArticle` (the `section.params.length > 0` branch) with a table, and drop the inline-styled flex rows: + +```tsx +{section.params.length > 0 ? ( +
+
+ Parameters +
+
+ + + + + + + + {section.params.map((param) => ( + + + + + ))} + +
ParameterDescription
+ + {param.name} + + + {renderInlineCode(param.description)} +
+

+) : null} +``` + +Then wrap the outer `
` content in a prose container by adding `className="cockpit-prose"` to a wrapping `
` around the language groups (keep the `section` for the scroll container): + +```tsx +
+
+ {/* existing tsSections / pySections blocks */} +
+
+``` + +- [ ] **Step 4: Add minimal table CSS** + +In `cockpit.css`, append: + +```css +.cockpit-prose table.params { border-collapse: collapse; margin: 0.5rem 0; } +.cockpit-prose table.params th { font-family: var(--font-mono), monospace; font-size: 0.6rem; letter-spacing: 0.06em; text-transform: uppercase; padding-bottom: 0.5rem; border-bottom: 1px solid var(--ds-border); } +.cockpit-prose table.params td { padding: 0.5rem 0.75rem 0.5rem 0; border-bottom: 1px solid var(--ds-border); } +``` + +- [ ] **Step 5: Run test to verify it passes** + +Run: `npx nx test cockpit -- src/components/api-mode/api-mode.spec.tsx` +Expected: PASS. + +- [ ] **Step 6: Browser verify (responsive)** + +API mode shows a prose column; params render as a table; on a narrow viewport the section scrolls without breaking layout. + +- [ ] **Step 7: Commit** + +```bash +git add apps/cockpit/src/components/api-mode/api-mode.tsx apps/cockpit/src/components/api-mode/api-mode.spec.tsx apps/cockpit/src/app/cockpit.css +git commit -m "refactor(cockpit): API mode prose container + responsive param table" +``` + +--- + +## Task 9: Full cross-theme verification + +**Files:** none (verification only) + +- [ ] **Step 1: Run the full cockpit test suite** + +Run: `npx nx test cockpit` +Expected: PASS (all specs green). + +- [ ] **Step 2: Run lint/typecheck** + +Run: `npx nx lint cockpit && npx nx typecheck cockpit` (use whichever targets exist in `apps/cockpit/project.json`). +Expected: PASS. + +- [ ] **Step 3: Manual browser pass (light + dark)** + +Serve cockpit, visit `deep-agents/core-capabilities/planning`. For BOTH themes (toggle in sidebar): +- Chrome: logo lockup present, no capability title, single sky-blue (`#64C3FD`) accent, mono labels in JetBrains Mono. +- Run mode: the embedded chat demo follows the host theme (postMessage handshake intact β€” verify by toggling and watching the iframe). +- Code mode: clean header, in-block horizontal scroll, header adapts to theme. +- Docs mode: prose column, serif headings, responsive at narrow width. +- API mode: prose container, responsive param table. + +- [ ] **Step 4: Final commit (if any verification fixups were needed)** + +```bash +git add -A && git commit -m "chore(cockpit): redesign verification fixups" +``` + +--- + +## Self-review notes + +- **Spec coverage:** shell/branding (T2–T4), fonts (T1), code mode unify + overflow (T5–T6), docs prose (T7), API prose+table (T8), light-mode preserved (verified T9 step 3). All five spec work areas covered. +- **Shared code block:** implemented as shared CSS (`.doc-codeblock`) rather than a new React component, because docs code blocks are produced server-side by `render-markdown.ts` while Code mode is React β€” a shared CSS contract unifies both producers with the least churn and keeps `render-markdown.spec.ts` green. +- **Prose:** implemented as a `.cockpit-prose` CSS class, not `@tailwindcss/typography` (not installed; project is Tailwind v4 with no config). This avoids a new dependency and matches the existing hand-rolled pattern. +- **Risk β€” light mode:** Tasks 5 and 8 remove the last hardcoded color literals; T9 step 3 explicitly checks both themes including the iframe handshake. diff --git a/docs/superpowers/specs/2026-05-29-cockpit-redesign-design.md b/docs/superpowers/specs/2026-05-29-cockpit-redesign-design.md new file mode 100644 index 000000000..d8705a1a0 --- /dev/null +++ b/docs/superpowers/specs/2026-05-29-cockpit-redesign-design.md @@ -0,0 +1,92 @@ +# Cockpit Redesign β€” Branded Dark Console, Chat-Aligned + +**Date:** 2026-05-29 +**Status:** Design β€” pending review +**Scope:** `apps/cockpit` (Next.js). No changes to `libs/chat`, the example graphs, or the marketing site beyond what is needed to share fonts/tokens. + +## Goal + +Redesign the cockpit web app so it (1) reads as a branded member of the Threadplane family, (2) aligns its palette and surfaces with the **chat library** so the embedded demo and the surrounding chrome form one seamless surface, and (3) presents Code / Docs / API content with the same polish as the marketing website. + +This is a **reskin + layout cleanup of the cockpit chrome and content modes**, not a re-architecture. The route resolution, manifest, mode-switching, and iframe runtime model stay as they are. + +## Direction (validated) + +Chosen direction: **Branded Dark Console** (keep the dark, focused, "Linear-style" app feel; add the missing brand layer). Rejected: a light editorial reskin (pulls toward the marketing voice, away from the chat library) and a per-product accent scheme (weakens shell↔demo cohesion). + +Locked decisions from brainstorming: + +- **Dark-first, single accent.** One accent β€” the chat library's sky-blue `#64C3FD` β€” everywhere: section labels, active states, links, and the demo's own accent. No per-product accent colors in the sidebar. +- **Sans chrome, no capability title.** The cockpit chrome stays sans-serif (Inter). The current small capability-title `

` in the header is **removed** entirely; the breadcrumb carries context. +- **Serif stays in docs only.** EB Garamond remains the heading face for narrative-docs articles (website alignment), and is *not* introduced into the chrome. +- **Light mode is preserved** and continues to propagate into the example iframe via the existing `ThemedFrame` β†’ `useEmbeddedTheme()` postMessage handshake. Nothing about that mechanism changes. + +## Palette & tokens (already defined, apply consistently) + +The design system already encodes a chat-aligned dark theme in `libs/design-tokens`. The redesign **consumes these tokens** rather than introducing new colors, and removes hardcoded color literals from cockpit components. + +| Role | Token | Value (dark) | +|------|-------|--------------| +| Canvas | `--ds-canvas` | `rgb(17,17,17)` | +| Surface | `--ds-surface` | `rgb(28,28,28)` | +| Surface tinted | `--ds-surface-tinted` | `rgb(44,44,44)` | +| Border | `--ds-border` | `rgb(45,45,45)` | +| Text primary / secondary / muted | `--ds-text-*` | `#f5f5f5` / `#c8c8c8` / `#a0a0a0` | +| Accent | `--ds-accent` | `#64C3FD` | + +Code-body syntax colors (Tokyo Night `#1a1b26` bg / `#a9b1d6` fg, JetBrains Mono 13px) already match the website's Shiki output and stay as-is in **both** themes. Only the code-block *chrome* (header bar) becomes theme-aware. + +## Work areas + +### 1. Shell chrome & branding β€” `cockpit-shell.tsx`, `cockpit-sidebar.tsx` + +- **Header (`cockpit-shell.tsx`):** remove the `entryTitle` `

` and the `|` divider. Keep the breadcrumb (`contextLabel`, mono, muted) on the left and the `ModeSwitcher` on the right. The header becomes a thin context+controls bar. +- **Brand lockup (`cockpit-sidebar.tsx`):** replace the bare mono `Cockpit` text with a Threadplane logo lockup β€” the bird mark + "Threadplane" wordmark and a mono `cockpit` qualifier. Reuse the website's logo asset. The `LanguagePicker` stays to its right. +- **Tokens:** ensure shell/sidebar use `--ds-*` tokens throughout (already mostly true). The existing `ThemeToggle` at the sidebar foot stays. + +### 2. Fonts β€” `apps/cockpit/src/app/layout.tsx` + +- Load **Inter, JetBrains Mono, EB Garamond** via `next/font/google`, exposing `--font-inter`, `--font-mono`, `--font-garamond` on `` β€” mirroring `apps/website/src/app/layout.tsx`. Today the cockpit loads none of these, so mono labels and serif docs headings fall back to system fonts. This makes the design tokens' font families actually render and matches the website. + +### 3. Code mode β€” `code-mode/code-mode.tsx`, new shared code-block component, `cockpit.css` + +- **Unify code-block chrome.** Create one shared code-block component used by **both** Code mode and narrative docs, replacing the divergent `.code-mode-block` (inline hardcoded `#a9b1d6` / `rgba(26,27,38,.95)` / `#4A527A`) and `.doc-codeblock` treatments. +- **Header:** short **filename only** (not the full repo path), a small language chip (`TS` / `PY` / `MD`), and a legible Copy button. Colors from tokens, so the header adapts to light/dark; the code body stays Tokyo Night in both. +- **Overflow fix:** the code **body** owns `overflow-x: auto` (long lines scroll inside the block); the mode pane owns vertical scroll. Long filenames truncate with ellipsis. This resolves the "overflow is not correct" issue where long lines widened the whole pane. +- **De-clutter:** tab strip shows filenames with the active tab underlined in `--ds-accent`; prompt files (`.md`) keep the accent tint they have today. + +### 4. Docs mode β€” `narrative-docs/narrative-docs.tsx`, `cockpit.css` + +- Replace the long inline `[&_h1]:…` arbitrary-variant chains with a shared **`prose` layer** (Tailwind Typography, `prose-invert` in dark) tuned to the website's article styling: ~42rem measure, serif `h1/h2/h3` (kept), Inter body, accent links, inline code in accent tint. +- Use the **shared code-block component** for fenced code; keep the existing callout / step / summary doc components, mapping their colors to tokens. +- Fully responsive single column with comfortable padding on mobile. + +### 5. API mode β€” `api-mode/api-mode.tsx` + +- Move into the **same prose container** as Docs for consistent measure and rhythm. +- Render signatures in the shared code-block style. +- Replace the cramped flex param rows with a real **responsive table** (Parameter / Type / Description). Keep grouping by language (TypeScript / Python) with a mono language label. + +## Out of scope + +- The empty-state content gaps ("No runtime available", "No documentation available") β€” these are content/runtime issues, not redesign. The redesign only restyles the empty-state messages to match. +- Any change to `libs/chat`, the example Angular apps, or the LangGraph graphs. +- Marketing website changes (other than reusing its logo asset and font setup as references). +- Information architecture / navigation restructuring. + +## Risks & notes + +- **Shared code-block component** is the only genuinely new abstraction; it must serve both server-highlighted HTML (Shiki, via `dangerouslySetInnerHTML`) in Code mode and fenced blocks inside markdown docs. Keep its API minimal: `{ filename, lang, html | children }`. +- **Tailwind Typography** (`@tailwindcss/typography`) may need adding to the cockpit's Tailwind setup if not already present; verify before relying on `prose`. +- **Light-mode regression risk:** every hardcoded color removed must be replaced with a token so light mode renders correctly; test both themes, and confirm the iframe still receives the theme after the reskin. +- **Patch release only** per project convention (`@ngaf`/`@threadplane` packages stay at `0.0.x`); the cockpit app itself isn't published, so this is moot for cockpit but relevant if shared libs (`ui-react`, `design-tokens`) change. + +## Verification + +- Run `npx nx serve cockpit` and exercise a capability page (e.g. `deep-agents/planning`) in **both** light and dark: + - Chrome: logo lockup present, no capability title, single sky-blue accent, mono labels rendering in JetBrains Mono. + - Run mode: embedded chat demo theme follows the host toggle (postMessage handshake intact). + - Code mode: clean header (filename + lang chip + Copy), long lines scroll inside the block, header adapts to theme. + - Docs mode: prose article, serif headings (EB Garamond actually loading), shared code block, responsive. + - API mode: prose container, responsive param table. +- Existing component specs (`*.spec.tsx`) updated to match the restructured markup.