Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
f2a07c1
docs(spec): adapter testing-surface alignment (phase 2)
blove May 29, 2026
dd750fa
docs(plan): adapter testing-surface alignment implementation plan
blove May 29, 2026
454a5bc
docs(spec): cockpit redesign — branded dark console, chat-aligned
blove May 29, 2026
b40a594
docs(plan): cockpit redesign implementation plan
blove May 29, 2026
1308d35
feat(cockpit): load brand webfonts (Inter, JetBrains Mono, EB Garamond)
blove May 29, 2026
58aad3b
feat(cockpit): add Threadplane logo lockup component
blove May 29, 2026
239d7c4
test(cockpit): use renderToStaticMarkup for Logo spec
blove May 29, 2026
81d2350
feat(cockpit): show Threadplane logo lockup in sidebar
blove May 29, 2026
a1b9404
refactor(cockpit): drop redundant capability title from header
blove May 29, 2026
6a6c0b8
fix(cockpit): make shared code block theme-aware and contain overflow
blove May 29, 2026
d8aa510
fix(cockpit): make code-body horizontal scroll explicit on .doc-codeb…
blove May 29, 2026
f7f911e
refactor(cockpit): code mode uses shared theme-aware code block
blove May 29, 2026
cde5e32
refactor(cockpit): dedupe filename, guard extensionless files, tighte…
blove May 29, 2026
c2fe2bc
refactor(cockpit): consolidate docs styling into shared .cockpit-prose
blove May 29, 2026
c1303db
refactor(cockpit): API mode prose container + responsive param table
blove May 29, 2026
0b9d660
chore: ignore all tsconfig.tsbuildinfo files
blove Jun 2, 2026
83c2026
Merge branch 'main' into claude/quirky-haslett-d443a4
blove Jun 2, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -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
54 changes: 35 additions & 19 deletions apps/cockpit/src/app/cockpit.css
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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); }
12 changes: 11 additions & 1 deletion apps/cockpit/src/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down Expand Up @@ -31,7 +36,12 @@ export default async function RootLayout({ children }: RootLayoutProps) {
const theme: Theme = cookieValue === 'light' ? 'light' : 'dark';

return (
<html lang="en" data-theme={theme} style={cssVars(theme) as React.CSSProperties}>
<html
lang="en"
data-theme={theme}
className={`${inter.variable} ${mono.variable} ${garamond.variable}`}
style={cssVars(theme) as React.CSSProperties}
>
<body
className="min-h-screen font-sans antialiased"
style={{
Expand Down
2 changes: 2 additions & 0 deletions apps/cockpit/src/components/api-mode/api-mode.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ describe('ApiMode', () => {
expect(html).toContain('stream');
expect(html).toContain('prompt');
expect(html).toContain('The user message');
expect(html).toContain('<table');
expect(html).toContain('Parameter');
expect(html).toContain('Observable emitting tokens');

expect(html).toContain('StreamingGraph');
Expand Down
99 changes: 52 additions & 47 deletions apps/cockpit/src/components/api-mode/api-mode.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,28 +62,31 @@ function DocArticle({ section }: { section: DocSection }) {

{section.params.length > 0 ? (
<div>
<h5
className="text-xs font-mono uppercase tracking-wide mb-1.5"
style={{ color: 'var(--ds-text-muted)' }}
>
<h5 className="text-xs font-mono uppercase tracking-wide mb-1.5" style={{ color: 'var(--ds-text-muted)' }}>
Parameters
</h5>
<div className="space-y-1">
{section.params.map((param) => (
<div key={param.name} className="flex gap-2 text-sm">
<code
className="shrink-0 px-1 py-0.5 rounded text-xs font-mono"
style={{
background: 'var(--ds-accent-surface)',
color: 'var(--ds-accent)',
}}
>
{param.name}
</code>
<span style={{ color: 'var(--ds-text-muted)' }}>{renderInlineCode(param.description)}</span>
</div>
))}
</div>
<table className="params w-full text-sm">
<thead>
<tr>
<th style={{ textAlign: 'left', color: 'var(--ds-text-muted)' }}>Parameter</th>
<th style={{ textAlign: 'left', color: 'var(--ds-text-muted)' }}>Description</th>
</tr>
</thead>
<tbody>
{section.params.map((param) => (
<tr key={param.name}>
<td style={{ verticalAlign: 'top', paddingRight: '1rem' }}>
<code className="px-1 py-0.5 rounded text-xs font-mono" style={{ background: 'var(--ds-accent-surface)', color: 'var(--ds-accent)' }}>
{param.name}
</code>
</td>
<td style={{ verticalAlign: 'top', color: 'var(--ds-text-muted)' }}>
{renderInlineCode(param.description)}
</td>
</tr>
))}
</tbody>
</table>
</div>
) : null}

Expand Down Expand Up @@ -127,34 +130,36 @@ export function ApiMode({ docSections, hasCodeFiles = false }: ApiModeProps) {
const pySections = docSections.filter((s) => s.language === 'python');

return (
<section aria-label="API mode" className="h-full overflow-auto space-y-6 py-2 px-1">
{tsSections.length > 0 ? (
<div>
<h3
className="text-xs font-mono uppercase tracking-wide mb-3"
style={{ color: 'var(--ds-accent)' }}
>
{LANGUAGE_LABELS[tsSections[0]?.language] ?? 'TypeScript'}
</h3>
{tsSections.map((section) => (
<DocArticle key={`${section.sourceFile}:${section.title}`} section={section} />
))}
</div>
) : null}
<section aria-label="API mode" className="h-full overflow-auto py-4 px-4 md:px-8">
<div className="cockpit-prose" style={{ maxWidth: '48rem' }}>
{tsSections.length > 0 ? (
<div>
<h3
className="text-xs font-mono uppercase tracking-wide mb-3"
style={{ color: 'var(--ds-accent)' }}
>
{LANGUAGE_LABELS[tsSections[0]?.language] ?? 'TypeScript'}
</h3>
{tsSections.map((section) => (
<DocArticle key={`${section.sourceFile}:${section.title}`} section={section} />
))}
</div>
) : null}

{pySections.length > 0 ? (
<div>
<h3
className="text-xs font-mono uppercase tracking-wide mb-3"
style={{ color: 'var(--ds-accent)' }}
>
{LANGUAGE_LABELS[pySections[0]?.language] ?? 'Python'}
</h3>
{pySections.map((section) => (
<DocArticle key={`${section.sourceFile}:${section.title}`} section={section} />
))}
</div>
) : null}
{pySections.length > 0 ? (
<div>
<h3
className="text-xs font-mono uppercase tracking-wide mb-3"
style={{ color: 'var(--ds-accent)' }}
>
{LANGUAGE_LABELS[pySections[0]?.language] ?? 'Python'}
</h3>
{pySections.map((section) => (
<DocArticle key={`${section.sourceFile}:${section.title}`} section={section} />
))}
</div>
) : null}
</div>
</section>
);
}
21 changes: 21 additions & 0 deletions apps/cockpit/src/components/branding/logo.spec.tsx
Original file line number Diff line number Diff line change
@@ -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(<Logo />);
expect(html).toContain('Threadplane');
});

it('renders the cockpit qualifier', () => {
const html = renderToStaticMarkup(<Logo />);
expect(html).toContain('cockpit');
});

it('exposes a stable data-ui selector', () => {
const html = renderToStaticMarkup(<Logo />);
expect(html).toContain('data-ui="cockpit-logo"');
});
});
21 changes: 21 additions & 0 deletions apps/cockpit/src/components/branding/logo.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import React from 'react';
import type { HTMLAttributes } from 'react';

export function Logo({ className, style, ...rest }: HTMLAttributes<HTMLSpanElement>) {
return (
<span
data-ui="cockpit-logo"
className={className}
style={{ display: 'inline-flex', alignItems: 'center', gap: '8px', lineHeight: 1, ...style }}
{...rest}
>
<span aria-hidden="true" style={{ fontSize: 20, lineHeight: 1 }}>🛩️</span>
<span style={{ fontFamily: 'var(--font-garamond), "EB Garamond", Georgia, serif', fontSize: 16, fontWeight: 600, color: 'var(--ds-text-primary)' }}>
Threadplane
</span>
<span style={{ fontFamily: 'var(--font-mono), "JetBrains Mono", monospace', fontSize: 12, color: 'var(--ds-text-muted)' }}>
cockpit
</span>
</span>
);
}
2 changes: 0 additions & 2 deletions apps/cockpit/src/components/cockpit-shell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -94,8 +94,6 @@ export function CockpitShell({
<MenuIcon />
</button>
<p className="hidden md:block text-[var(--ds-text-muted)] font-mono text-xs">{contextLabel}</p>
<span className="hidden md:block text-[var(--ds-accent-border)]">|</span>
<h2 className="text-sm font-medium text-[var(--ds-text-primary)]">{entryTitle}</h2>
</div>
<div className="overflow-x-auto -mx-4 px-4 md:mx-0 md:px-0">
<ModeSwitcher
Expand Down
2 changes: 2 additions & 0 deletions apps/cockpit/src/components/code-mode/code-mode.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ describe('CodeMode', () => {
});

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"]'));
Expand Down
44 changes: 12 additions & 32 deletions apps/cockpit/src/components/code-mode/code-mode.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,43 +28,23 @@ function CodeFileContent({
return <p className="text-sm text-[var(--ds-text-muted)]">No source available for {getTabLabel(path)}</p>;
}

const label = getTabLabel(path);
const dotIdx = label.lastIndexOf('.');
const ext = dotIdx > 0 ? label.slice(dotIdx + 1).toUpperCase() : '';

return (
<div className="code-mode-block" style={{
borderRadius: 'var(--ds-radius-md)',
border: '1px solid var(--ds-border)',
boxShadow: 'var(--ds-shadow-sm)',
overflow: 'hidden',
}}>
<div
className="border-b border-[var(--ds-border)]"
style={{
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
padding: '6px 12px',
background: 'rgba(26, 27, 38, 0.95)',
}}>
<span style={{ fontFamily: 'var(--ds-font-mono)', fontSize: '0.7rem', color: '#a9b1d6' }}>{path}</span>
<div className="doc-codeblock">
<div className="doc-codeblock__header">
<span className="doc-codeblock__file">{label}</span>
{ext ? <span className="doc-codeblock__lang">{ext}</span> : null}
<button
aria-label={`Copy ${getTabLabel(path)}`}
className="doc-codeblock__copy"
aria-label={`Copy ${label}`}
onClick={() => {
track('cockpit:code_copied', {
capability,
surface: 'code_mode',
file_path: path,
});
track('cockpit:code_copied', { capability, surface: 'code_mode', file_path: path });
const el = document.querySelector(`[data-code-path="${CSS.escape(path)}"] pre code`);
if (el) navigator.clipboard.writeText(el.textContent ?? '');
}}
style={{
padding: '2px 8px',
borderRadius: 4,
border: '1px solid rgba(255,255,255,0.1)',
background: 'transparent',
color: '#4A527A',
fontSize: '0.65rem',
cursor: 'pointer',
}}
>Copy</button>
</div>
<div data-code-path={path} dangerouslySetInnerHTML={{ __html: content }} />
Expand Down Expand Up @@ -108,7 +88,7 @@ export function CodeMode({ entryTitle, codeAssetPaths, backendAssetPaths, codeFi
</TabsList>

{[...codeAssetPaths, ...backendAssetPaths].map((path) => (
<TabsContent key={path} value={path} className="flex-1 overflow-auto mt-4">
<TabsContent key={path} value={path} className="flex-1 overflow-auto">
<CodeFileContent path={path} content={codeFiles[path]} capability={capability} />
</TabsContent>
))}
Expand Down
11 changes: 1 addition & 10 deletions apps/cockpit/src/components/narrative-docs/narrative-docs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,16 +55,7 @@ export function NarrativeDocs({ narrativeDocs, capability }: NarrativeDocsProps)
<article
key={doc.sourceFile}
onClick={handleClick}
className="docs-article max-w-3xl text-[0.9rem] leading-relaxed text-[var(--ds-text-secondary)]
[&_h1]:font-semibold [&_h1]:tracking-tight [&_h1]:text-[var(--ds-text-primary)] [&_h1]:mb-2 [&_h1]:pb-3 [&_h1]:border-b [&_h1]:border-[var(--ds-accent-border)]
[&_h2]:font-semibold [&_h2]:tracking-tight [&_h2]:text-[var(--ds-text-primary)] [&_h2]:mt-10 [&_h2]:mb-3
[&_h3]:font-semibold [&_h3]:text-[var(--ds-text-primary)] [&_h3]:mt-6 [&_h3]:mb-2
[&_p]:mb-3 [&_p]:leading-relaxed
[&_ul]:mb-3 [&_ul]:pl-5 [&_ul]:list-disc
[&_li]:mb-1 [&_li]:leading-relaxed
[&_a]:text-[var(--ds-accent)] [&_a]:no-underline hover:[&_a]:underline
[&_code]:text-[var(--ds-accent)] [&_code]:bg-[var(--ds-accent-surface)] [&_code]:px-1 [&_code]:py-0.5 [&_code]:rounded [&_code]:text-[0.85em] [&_code]:font-mono
[&_strong]:text-[var(--ds-text-primary)] [&_strong]:font-semibold"
className="docs-article cockpit-prose"
dangerouslySetInnerHTML={{ __html: doc.html }}
/>
))}
Expand Down
3 changes: 2 additions & 1 deletion apps/cockpit/src/components/sidebar/cockpit-sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand All @@ -29,7 +30,7 @@ export function CockpitSidebar({
}}
>
<header className="flex items-center justify-between px-4">
<p className="text-[var(--ds-text-muted)] font-mono text-xs font-medium tracking-wide uppercase">Cockpit</p>
<Logo />
<LanguagePicker manifest={manifest} entry={entry} />
</header>
<NavigationGroups tree={navigationTree} currentEntry={entry} />
Expand Down
Loading
Loading