diff --git a/apps/website/e2e/website.spec.ts b/apps/website/e2e/website.spec.ts index fc191a4b9..e1bf0592e 100644 --- a/apps/website/e2e/website.spec.ts +++ b/apps/website/e2e/website.spec.ts @@ -34,7 +34,7 @@ test('pricing page lead form validates required fields', async ({ page }) => { test('docs page renders sidebar and content', async ({ page }) => { await page.goto('/docs/getting-started/introduction'); - await expect(page.locator('aside')).toBeVisible(); + await expect(page.locator('aside').first()).toBeVisible(); await expect(page.locator('article')).toBeVisible(); }); diff --git a/apps/website/next-env.d.ts b/apps/website/next-env.d.ts index fdbfe5258..c4b7818fb 100644 --- a/apps/website/next-env.d.ts +++ b/apps/website/next-env.d.ts @@ -1,6 +1,6 @@ /// /// -import "./../../dist/apps/website/.next/types/routes.d.ts"; +import "./.next/dev/types/routes.d.ts"; // NOTE: This file should not be edited // see https://nextjs.org/docs/app/api-reference/config/typescript for more information. diff --git a/apps/website/src/app/docs/[[...slug]]/page.tsx b/apps/website/src/app/docs/[[...slug]]/page.tsx index c30d4ff41..558792999 100644 --- a/apps/website/src/app/docs/[[...slug]]/page.tsx +++ b/apps/website/src/app/docs/[[...slug]]/page.tsx @@ -4,6 +4,8 @@ import { MdxRendererNew } from '../../../components/docs/MdxRenderer'; import { DocsSearch } from '../../../components/docs/DocsSearch'; import { getDocBySlug, getAllDocSlugs } from '../../../lib/docs-new'; import { ApiDocRenderer, type ApiDocEntry } from '../../../components/docs/ApiDocRenderer'; +import { DocsTOC } from '../../../components/docs/DocsTOC'; +import { extractHeadings } from '../../../lib/extract-headings'; import fs from 'fs'; import path from 'path'; @@ -44,18 +46,21 @@ export default async function DocsPage({ params }: { params: Promise<{ slug?: st
-
- - {section === 'api' && (() => { - const entries = loadApiDocs(); - const target = API_NAME_MAP[slug]; - const apiEntry = target ? entries.find((e: ApiDocEntry) => e.name === target) : null; - return apiEntry ? ( -
- -
- ) : null; - })()} +
+
+ + {section === 'api' && (() => { + const entries = loadApiDocs(); + const target = API_NAME_MAP[slug]; + const apiEntry = target ? entries.find((e: ApiDocEntry) => e.name === target) : null; + return apiEntry ? ( +
+ +
+ ) : null; + })()} +
+
); diff --git a/apps/website/src/app/global.css b/apps/website/src/app/global.css index 9a7be9bec..7499ec889 100644 --- a/apps/website/src/app/global.css +++ b/apps/website/src/app/global.css @@ -82,3 +82,67 @@ html { font-size: 0.75rem; line-height: 1.7; } + +/* rehype-pretty-code — docs code blocks */ +.docs-prose [data-rehype-pretty-code-figure] { + margin: 1.5rem 0; +} + +.docs-prose [data-rehype-pretty-code-figure] pre { + padding: 1.25rem 1.5rem; + border-radius: 0.75rem; + border: 1px solid rgba(255, 255, 255, 0.6); + box-shadow: 0 4px 24px rgba(0, 0, 0, 0.06); + overflow-x: auto; + font-size: 0.8rem; + line-height: 1.7; +} + +.docs-prose [data-rehype-pretty-code-figure] code { + font-family: var(--font-mono), monospace; + font-size: inherit; + background: none !important; + padding: 0 !important; + border-radius: 0 !important; + color: inherit !important; +} + +.docs-prose [data-rehype-pretty-code-figure] [data-line] { + padding: 0 0.25rem; +} + +.docs-prose [data-rehype-pretty-code-figure] [data-rehype-pretty-code-title] { + font-family: var(--font-mono), monospace; + font-size: 0.7rem; + color: #8b8fa3; + padding: 0.5rem 1.5rem; + background: #1a1b26; + border-bottom: 1px solid rgba(255, 255, 255, 0.06); + border-radius: 0.75rem 0.75rem 0 0; +} + +.docs-prose [data-rehype-pretty-code-figure]:has([data-rehype-pretty-code-title]) pre { + border-radius: 0 0 0.75rem 0.75rem; +} + +.docs-prose :not(pre) > code { + font-family: var(--font-mono), monospace; + font-size: 0.85em; + background: rgba(0, 64, 144, 0.06); + color: #004090; + padding: 0.15rem 0.4rem; + border-radius: 0.25rem; + font-weight: 400; +} + +.docs-prose h1 { font-size: 1.875rem; font-weight: 700; margin-top: 0; margin-bottom: 1rem; font-family: var(--font-garamond); } +.docs-prose h2 { font-size: 1.5rem; font-weight: 600; margin-top: 2.5rem; margin-bottom: 1rem; font-family: var(--font-garamond); } +.docs-prose h3 { font-size: 1.25rem; font-weight: 600; margin-top: 2rem; margin-bottom: 0.75rem; font-family: var(--font-garamond); } +.docs-prose p { line-height: 1.75; margin-bottom: 1.25rem; } +.docs-prose ul, .docs-prose ol { margin-bottom: 1.25rem; } +.docs-prose li { margin-bottom: 0.25rem; } + +.docs-prose table { width: 100%; border-collapse: collapse; font-size: 0.875rem; margin: 1.5rem 0; } +.docs-prose th { text-align: left; padding: 0.5rem 0.75rem; font-family: var(--font-mono); font-size: 0.75rem; text-transform: uppercase; color: #8b8fa3; border-bottom: 1px solid rgba(0, 64, 144, 0.15); } +.docs-prose td { padding: 0.5rem 0.75rem; border-bottom: 1px solid rgba(0, 64, 144, 0.08); color: #555770; } +.docs-prose td code { font-size: 0.8em; } diff --git a/apps/website/src/components/docs/DocsTOC.tsx b/apps/website/src/components/docs/DocsTOC.tsx new file mode 100644 index 000000000..9c12354c9 --- /dev/null +++ b/apps/website/src/components/docs/DocsTOC.tsx @@ -0,0 +1,55 @@ +'use client'; +import { useState, useEffect } from 'react'; +import { tokens } from '../../../lib/design-tokens'; +import type { DocHeading } from '../../lib/extract-headings'; + +export function DocsTOC({ headings }: { headings: DocHeading[] }) { + const [activeId, setActiveId] = useState(''); + + useEffect(() => { + const observer = new IntersectionObserver( + (entries) => { + for (const entry of entries) { + if (entry.isIntersecting) { + setActiveId(entry.target.id); + } + } + }, + { rootMargin: '-80px 0px -80% 0px' }, + ); + + for (const heading of headings) { + const el = document.getElementById(heading.id); + if (el) observer.observe(el); + } + + return () => observer.disconnect(); + }, [headings]); + + if (headings.length === 0) return null; + + return ( + + ); +} diff --git a/apps/website/src/components/docs/MdxRenderer.tsx b/apps/website/src/components/docs/MdxRenderer.tsx index 3411bb4e5..f25c13a0b 100644 --- a/apps/website/src/components/docs/MdxRenderer.tsx +++ b/apps/website/src/components/docs/MdxRenderer.tsx @@ -1,5 +1,4 @@ import { MDXRemote } from 'next-mdx-remote/rsc'; -import { CopyPromptButton } from './CopyPromptButton'; import { tokens } from '../../../lib/design-tokens'; import { Callout } from './mdx/Callout'; import { Steps, Step } from './mdx/Steps'; @@ -8,6 +7,7 @@ import { Card, CardGroup } from './mdx/Card'; import { CodeGroup } from './mdx/CodeGroup'; import { DocsBreadcrumb } from './DocsBreadcrumb'; import { DocsPrevNext } from './DocsPrevNext'; +import rehypePrettyCode from 'rehype-pretty-code'; const mdxComponents = { Callout, @@ -20,30 +20,10 @@ const mdxComponents = { CodeGroup, }; -interface Props { - source: string; - prompt?: string; -} - -/** Legacy renderer for old cockpit-based docs */ -export function MdxRenderer({ source, prompt }: Props) { - return ( -
- {prompt && ( -
- -
- )} - -
- ); -} +const rehypeOptions = { + theme: 'tokyo-night', + keepBackground: true, +}; interface NewProps { source: string; @@ -52,19 +32,26 @@ interface NewProps { title: string; } -/** New renderer with custom MDX components, breadcrumbs, and prev/next */ export function MdxRendererNew({ source, section, slug, title }: NewProps) { return ( -
+
-
- +
diff --git a/apps/website/src/components/docs/mdx/Callout.tsx b/apps/website/src/components/docs/mdx/Callout.tsx index c5f321f6c..7eff403a3 100644 --- a/apps/website/src/components/docs/mdx/Callout.tsx +++ b/apps/website/src/components/docs/mdx/Callout.tsx @@ -1,10 +1,10 @@ import { tokens } from '../../../../lib/design-tokens'; const CALLOUT_STYLES = { - info: { border: tokens.colors.accent, bg: 'rgba(0, 64, 144, 0.04)' }, - warning: { border: '#f59e0b', bg: 'rgba(245, 158, 11, 0.04)' }, - tip: { border: '#22c55e', bg: 'rgba(34, 197, 94, 0.04)' }, - danger: { border: '#ef4444', bg: 'rgba(239, 68, 68, 0.04)' }, + info: { border: tokens.colors.accent, bg: 'rgba(0, 64, 144, 0.05)', icon: 'ℹ️' }, + warning: { border: '#f59e0b', bg: 'rgba(245, 158, 11, 0.05)', icon: '⚠️' }, + tip: { border: '#22c55e', bg: 'rgba(34, 197, 94, 0.05)', icon: '💡' }, + danger: { border: '#ef4444', bg: 'rgba(239, 68, 68, 0.05)', icon: '🚫' }, } as const; interface Props { @@ -14,25 +14,30 @@ interface Props { } export function Callout({ type = 'info', title, children }: Props) { - const style = CALLOUT_STYLES[type]; + const s = CALLOUT_STYLES[type]; return (
{title && (
{title}
+ color: s.border, + marginBottom: 6, + }}> + {s.icon} + {title} +
)} -
+
{children}
diff --git a/apps/website/src/components/docs/mdx/Card.tsx b/apps/website/src/components/docs/mdx/Card.tsx index 9654dad59..ac75f48ba 100644 --- a/apps/website/src/components/docs/mdx/Card.tsx +++ b/apps/website/src/components/docs/mdx/Card.tsx @@ -1,3 +1,4 @@ +'use client'; import Link from 'next/link'; import { tokens } from '../../../../lib/design-tokens'; @@ -5,9 +6,10 @@ export function CardGroup({ cols = 2, children }: { cols?: number; children: Rea return (
{children}
@@ -17,24 +19,40 @@ export function CardGroup({ cols = 2, children }: { cols?: number; children: Rea export function Card({ title, href, icon, children }: { title: string; href: string; icon?: string; children: React.ReactNode }) { return ( -
- {icon &&
{icon}
} -
{title}
+
{ + e.currentTarget.style.boxShadow = tokens.glow.card; + e.currentTarget.style.borderColor = tokens.colors.accentBorderHover; + e.currentTarget.style.transform = 'translateY(-1px)'; + }} + onMouseLeave={(e) => { + e.currentTarget.style.boxShadow = 'none'; + e.currentTarget.style.borderColor = tokens.glass.border; + e.currentTarget.style.transform = 'translateY(0)'; + }}> +
+
+ {icon &&
{icon}
} +
{title}
+
+ +
{children}
diff --git a/apps/website/src/components/docs/mdx/Tabs.tsx b/apps/website/src/components/docs/mdx/Tabs.tsx index 0e280bba0..772f8756d 100644 --- a/apps/website/src/components/docs/mdx/Tabs.tsx +++ b/apps/website/src/components/docs/mdx/Tabs.tsx @@ -8,26 +8,28 @@ export function Tabs({ items, children }: { items?: string[]; children: React.Re const labels = items ?? tabs.map((_, i) => `Tab ${i + 1}`); return ( -
+
{labels.map((label, i) => ( diff --git a/apps/website/src/lib/extract-headings.ts b/apps/website/src/lib/extract-headings.ts new file mode 100644 index 000000000..5438188c2 --- /dev/null +++ b/apps/website/src/lib/extract-headings.ts @@ -0,0 +1,29 @@ +export interface DocHeading { + id: string; + text: string; + level: number; +} + +/** Extract ## and ### headings from MDX source for TOC */ +export function extractHeadings(source: string): DocHeading[] { + const lines = source.split('\n'); + const headings: DocHeading[] = []; + let inCodeBlock = false; + + for (const line of lines) { + if (line.trim().startsWith('```')) { + inCodeBlock = !inCodeBlock; + continue; + } + if (inCodeBlock) continue; + + const match = line.match(/^(#{2,3})\s+(.+)$/); + if (match) { + const text = match[2].replace(/`/g, ''); + const id = text.toLowerCase().replace(/[^\w\s-]/g, '').replace(/\s+/g, '-'); + headings.push({ id, text, level: match[1].length }); + } + } + + return headings; +} diff --git a/docs/superpowers/plans/2026-04-04-docs-mintlify-alignment.md b/docs/superpowers/plans/2026-04-04-docs-mintlify-alignment.md new file mode 100644 index 000000000..fed864bd9 --- /dev/null +++ b/docs/superpowers/plans/2026-04-04-docs-mintlify-alignment.md @@ -0,0 +1,562 @@ +# Docs Mintlify Alignment + Code Highlighting Fix + +> **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:** Fix broken code block syntax highlighting in docs and align docs page design with Mintlify patterns — three-column layout with TOC, polished MDX components, and consistent typography. + +**Architecture:** Install `rehype-pretty-code` for Shiki-based code highlighting in the MDX pipeline. Create a `DocsTOC` component that extracts `##` headings from MDX source. Polish existing MDX components (Callout icons, Card hover, Tabs styling). Update global CSS with prose code block styles. + +**Tech Stack:** rehype-pretty-code, Shiki (tokyo-night), next-mdx-remote/rsc, Tailwind CSS v4, design tokens + +**Spec:** `docs/superpowers/specs/2026-04-04-docs-mintlify-alignment-design.md` + +--- + +### Task 1: Install rehype-pretty-code and Fix Code Highlighting + +**Files:** +- Modify: `package.json` (install dep) +- Modify: `apps/website/src/components/docs/MdxRenderer.tsx` +- Modify: `apps/website/src/app/global.css` + +- [ ] **Step 1: Install rehype-pretty-code** + +Run: `npm install rehype-pretty-code` + +- [ ] **Step 2: Update MdxRendererNew to use rehype-pretty-code** + +Replace the full file `apps/website/src/components/docs/MdxRenderer.tsx`: + +```tsx +import { MDXRemote } from 'next-mdx-remote/rsc'; +import { tokens } from '../../../lib/design-tokens'; +import { Callout } from './mdx/Callout'; +import { Steps, Step } from './mdx/Steps'; +import { Tabs, Tab } from './mdx/Tabs'; +import { Card, CardGroup } from './mdx/Card'; +import { CodeGroup } from './mdx/CodeGroup'; +import { DocsBreadcrumb } from './DocsBreadcrumb'; +import { DocsPrevNext } from './DocsPrevNext'; +import rehypePrettyCode from 'rehype-pretty-code'; + +const mdxComponents = { + Callout, + Steps, + Step, + Tabs, + Tab, + Card, + CardGroup, + CodeGroup, +}; + +const rehypeOptions = { + theme: 'tokyo-night', + keepBackground: true, +}; + +interface NewProps { + source: string; + section: string; + slug: string; + title: string; +} + +export function MdxRendererNew({ source, section, slug, title }: NewProps) { + return ( +
+ +
+ +
+ +
+ ); +} +``` + +- [ ] **Step 3: Add code block CSS styles to global.css** + +Add these styles after the existing `.shiki` styles in `apps/website/src/app/global.css`: + +```css +/* rehype-pretty-code — docs code blocks */ +.docs-prose [data-rehype-pretty-code-figure] { + margin: 1.5rem 0; +} + +.docs-prose [data-rehype-pretty-code-figure] pre { + padding: 1.25rem 1.5rem; + border-radius: 0.75rem; + border: 1px solid rgba(255, 255, 255, 0.6); + box-shadow: 0 4px 24px rgba(0, 0, 0, 0.06); + overflow-x: auto; + font-size: 0.8rem; + line-height: 1.7; +} + +.docs-prose [data-rehype-pretty-code-figure] code { + font-family: var(--font-mono), monospace; + font-size: inherit; + background: none !important; + padding: 0 !important; + border-radius: 0 !important; + color: inherit !important; +} + +.docs-prose [data-rehype-pretty-code-figure] [data-line] { + padding: 0 0.25rem; +} + +/* Language label */ +.docs-prose [data-rehype-pretty-code-figure] [data-rehype-pretty-code-title] { + font-family: var(--font-mono), monospace; + font-size: 0.7rem; + color: #8b8fa3; + padding: 0.5rem 1.5rem; + background: #1a1b26; + border-bottom: 1px solid rgba(255, 255, 255, 0.06); + border-radius: 0.75rem 0.75rem 0 0; +} + +.docs-prose [data-rehype-pretty-code-figure]:has([data-rehype-pretty-code-title]) pre { + border-radius: 0 0 0.75rem 0.75rem; +} + +/* Inline code in prose (not in code blocks) */ +.docs-prose :not(pre) > code { + font-family: var(--font-mono), monospace; + font-size: 0.85em; + background: rgba(0, 64, 144, 0.06); + color: #004090; + padding: 0.15rem 0.4rem; + border-radius: 0.25rem; + font-weight: 400; +} + +/* Prose heading spacing */ +.docs-prose h1 { font-size: 1.875rem; font-weight: 700; margin-top: 0; margin-bottom: 1rem; font-family: var(--font-garamond); } +.docs-prose h2 { font-size: 1.5rem; font-weight: 600; margin-top: 2.5rem; margin-bottom: 1rem; font-family: var(--font-garamond); } +.docs-prose h3 { font-size: 1.25rem; font-weight: 600; margin-top: 2rem; margin-bottom: 0.75rem; font-family: var(--font-garamond); } +.docs-prose p { line-height: 1.75; margin-bottom: 1.25rem; } +.docs-prose ul, .docs-prose ol { margin-bottom: 1.25rem; } +.docs-prose li { margin-bottom: 0.25rem; } + +/* Table styling */ +.docs-prose table { width: 100%; border-collapse: collapse; font-size: 0.875rem; margin: 1.5rem 0; } +.docs-prose th { text-align: left; padding: 0.5rem 0.75rem; font-family: var(--font-mono); font-size: 0.75rem; text-transform: uppercase; color: #8b8fa3; border-bottom: 1px solid rgba(0, 64, 144, 0.15); } +.docs-prose td { padding: 0.5rem 0.75rem; border-bottom: 1px solid rgba(0, 64, 144, 0.08); color: #555770; } +.docs-prose td code { font-size: 0.8em; } +``` + +- [ ] **Step 4: Build and verify** + +Run: `npx nx build website --skip-nx-cache 2>&1 | tail -5` +Expected: Build succeeds + +- [ ] **Step 5: Commit** + +```bash +git add package.json package-lock.json apps/website/src/components/docs/MdxRenderer.tsx apps/website/src/app/global.css +git commit -m "feat(website): fix code highlighting with rehype-pretty-code + prose styles" +``` + +--- + +### Task 2: Create DocsTOC — "On This Page" Right Sidebar + +**Files:** +- Create: `apps/website/src/components/docs/DocsTOC.tsx` +- Modify: `apps/website/src/app/docs/[[...slug]]/page.tsx` + +- [ ] **Step 1: Create the TOC component** + +Create `apps/website/src/components/docs/DocsTOC.tsx`: + +```tsx +'use client'; +import { useState, useEffect } from 'react'; +import { tokens } from '../../../lib/design-tokens'; + +interface Heading { + id: string; + text: string; + level: number; +} + +export function DocsTOC({ headings }: { headings: Heading[] }) { + const [activeId, setActiveId] = useState(''); + + useEffect(() => { + const observer = new IntersectionObserver( + (entries) => { + for (const entry of entries) { + if (entry.isIntersecting) { + setActiveId(entry.target.id); + } + } + }, + { rootMargin: '-80px 0px -80% 0px' }, + ); + + for (const heading of headings) { + const el = document.getElementById(heading.id); + if (el) observer.observe(el); + } + + return () => observer.disconnect(); + }, [headings]); + + if (headings.length === 0) return null; + + return ( + + ); +} + +/** Extract ## and ### headings from MDX source for TOC */ +export function extractHeadings(source: string): Heading[] { + const lines = source.split('\n'); + const headings: Heading[] = []; + let inCodeBlock = false; + + for (const line of lines) { + if (line.trim().startsWith('```')) { + inCodeBlock = !inCodeBlock; + continue; + } + if (inCodeBlock) continue; + + const match = line.match(/^(#{2,3})\s+(.+)$/); + if (match) { + const text = match[2].replace(/`/g, ''); + const id = text.toLowerCase().replace(/[^\w\s-]/g, '').replace(/\s+/g, '-'); + headings.push({ id, text, level: match[1].length }); + } + } + + return headings; +} +``` + +- [ ] **Step 2: Update docs page to include TOC** + +Read the current `apps/website/src/app/docs/[[...slug]]/page.tsx`, then update it to import and render the TOC. Add these imports at the top: + +```typescript +import { DocsTOC, extractHeadings } from '../../../components/docs/DocsTOC'; +``` + +Then in the JSX, change the content area from: +```tsx +
+ + {section === 'api' && ...} +
+``` + +To: +```tsx +
+
+ + {section === 'api' && (() => { + const entries = loadApiDocs(); + const target = API_NAME_MAP[slug]; + const apiEntry = target ? entries.find((e: ApiDocEntry) => e.name === target) : null; + return apiEntry ? ( +
+ +
+ ) : null; + })()} +
+ +
+``` + +- [ ] **Step 3: Commit** + +```bash +git add apps/website/src/components/docs/DocsTOC.tsx 'apps/website/src/app/docs/[[...slug]]/page.tsx' +git commit -m "feat(website): add On This Page TOC sidebar for docs" +``` + +--- + +### Task 3: Polish Callout Component — Add Icons + +**Files:** +- Modify: `apps/website/src/components/docs/mdx/Callout.tsx` + +- [ ] **Step 1: Replace Callout with icon-enhanced version** + +Replace `apps/website/src/components/docs/mdx/Callout.tsx`: + +```tsx +import { tokens } from '../../../../lib/design-tokens'; + +const CALLOUT_STYLES = { + info: { border: tokens.colors.accent, bg: 'rgba(0, 64, 144, 0.05)', icon: 'ℹ️' }, + warning: { border: '#f59e0b', bg: 'rgba(245, 158, 11, 0.05)', icon: '⚠️' }, + tip: { border: '#22c55e', bg: 'rgba(34, 197, 94, 0.05)', icon: '💡' }, + danger: { border: '#ef4444', bg: 'rgba(239, 68, 68, 0.05)', icon: '🚫' }, +} as const; + +interface Props { + type?: keyof typeof CALLOUT_STYLES; + title?: string; + children: React.ReactNode; +} + +export function Callout({ type = 'info', title, children }: Props) { + const s = CALLOUT_STYLES[type]; + return ( +
+ {title && ( +
+ {s.icon} + {title} +
+ )} +
+ {children} +
+
+ ); +} +``` + +- [ ] **Step 2: Commit** + +```bash +git add apps/website/src/components/docs/mdx/Callout.tsx +git commit -m "feat(website): add icons and polish Callout component" +``` + +--- + +### Task 4: Polish Card Component — Hover Effects + Arrow + +**Files:** +- Modify: `apps/website/src/components/docs/mdx/Card.tsx` + +- [ ] **Step 1: Replace Card with hover-enhanced version** + +Replace `apps/website/src/components/docs/mdx/Card.tsx`: + +```tsx +'use client'; +import Link from 'next/link'; +import { tokens } from '../../../../lib/design-tokens'; + +export function CardGroup({ cols = 2, children }: { cols?: number; children: React.ReactNode }) { + return ( +
+ {children} +
+ ); +} + +export function Card({ title, href, icon, children }: { title: string; href: string; icon?: string; children: React.ReactNode }) { + return ( + +
{ + e.currentTarget.style.boxShadow = tokens.glow.card; + e.currentTarget.style.borderColor = tokens.colors.accentBorderHover; + e.currentTarget.style.transform = 'translateY(-1px)'; + }} + onMouseLeave={(e) => { + e.currentTarget.style.boxShadow = 'none'; + e.currentTarget.style.borderColor = tokens.glass.border; + e.currentTarget.style.transform = 'translateY(0)'; + }}> +
+
+ {icon &&
{icon}
} +
{title}
+
+ +
+
+ {children} +
+
+ + ); +} +``` + +- [ ] **Step 2: Commit** + +```bash +git add apps/website/src/components/docs/mdx/Card.tsx +git commit -m "feat(website): add hover effects and arrow to Card component" +``` + +--- + +### Task 5: Polish Tabs Component + +**Files:** +- Modify: `apps/website/src/components/docs/mdx/Tabs.tsx` + +- [ ] **Step 1: Replace Tabs with polished version** + +Replace `apps/website/src/components/docs/mdx/Tabs.tsx`: + +```tsx +'use client'; +import { useState, Children } from 'react'; +import { tokens } from '../../../../lib/design-tokens'; + +export function Tabs({ items, children }: { items?: string[]; children: React.ReactNode }) { + const [active, setActive] = useState(0); + const tabs = Children.toArray(children); + const labels = items ?? tabs.map((_, i) => `Tab ${i + 1}`); + + return ( +
+
+ {labels.map((label, i) => ( + + ))} +
+
{tabs[active]}
+
+ ); +} + +export function Tab({ children }: { children: React.ReactNode }) { + return
{children}
; +} +``` + +- [ ] **Step 2: Commit** + +```bash +git add apps/website/src/components/docs/mdx/Tabs.tsx +git commit -m "feat(website): polish Tabs component styling" +``` + +--- + +### Task 6: Verify All Pages + +- [ ] **Step 1: Build the website** + +Run: `npx nx build website --skip-nx-cache 2>&1 | tail -5` +Expected: Build succeeds + +- [ ] **Step 2: Visual verification** + +Open these pages and verify: +- http://localhost:3000/docs/getting-started/quickstart — code blocks highlighted, Callout has icon +- http://localhost:3000/docs/guides/streaming — Tabs with highlighted code inside +- http://localhost:3000/docs/guides/testing — multiple code blocks highlighted +- http://localhost:3000/docs/concepts/angular-signals — inline code styled + +Verify on desktop: +- "On This Page" TOC appears on the right +- TOC highlights active section on scroll +- Code blocks have rounded corners and glass borders + +- [ ] **Step 3: Commit any fixes** + +```bash +git add -A +git commit -m "fix(website): docs design polish and fixes" +``` diff --git a/docs/superpowers/specs/2026-04-04-docs-mintlify-alignment-design.md b/docs/superpowers/specs/2026-04-04-docs-mintlify-alignment-design.md new file mode 100644 index 000000000..5c4871e32 --- /dev/null +++ b/docs/superpowers/specs/2026-04-04-docs-mintlify-alignment-design.md @@ -0,0 +1,98 @@ +# Docs Page Design — Mintlify Alignment + Code Highlighting Fix + +**Date:** 2026-04-04 +**Scope:** Docs page layout, code highlighting, MDX component polish + +## Overview + +Fix broken code block syntax highlighting in docs and align the docs page design with Mintlify patterns — three-column layout, polished typography, icons on callouts, hover effects on cards, and an "On This Page" table of contents. + +## Design Decisions + +| Decision | Choice | +|----------|--------| +| Code highlighting | `rehype-pretty-code` (Shiki-based rehype plugin for MDX) | +| Theme | `tokyo-night` (matches landing page) | +| Layout | Three-column: sidebar + content + right TOC | +| TOC | Extract `##` headings from MDX source, render as right sidebar | +| Callout icons | Unicode icons per type (info, warning, tip, danger) | +| Card hover | Scale + glow effect | + +## Fix 1: Code Block Highlighting + +**Root cause:** `MDXRemote` renders plain `
` without Shiki.
+
+**Fix:** Add `rehype-pretty-code` to MDXRemote's rehype pipeline. Runs at compile time on the server.
+
+```typescript
+import rehypePrettyCode from 'rehype-pretty-code';
+
+
+```
+
+**CSS needed:** Style the generated `[data-rehype-pretty-code-figure]` elements, `[data-language]` labels, and add a copy button.
+
+## Fix 2: Mintlify-Aligned Layout
+
+### Three-Column Layout
+- Left sidebar: 256px (existing `DocsSidebarNew`)
+- Content: `max-w-3xl` (existing)
+- Right TOC: 200px, sticky, hidden below `xl` breakpoint
+- TOC component extracts `##` headings from the MDX source string via regex
+
+### Typography
+- Page title: `text-3xl font-bold` with description subtitle
+- Body: `leading-relaxed`
+- Inline code: glass background `rgba(0,64,144,0.06)`, `rounded`, `px-1.5 py-0.5`, mono font
+- Headings: clear hierarchy with `mt-10 mb-4` spacing
+
+### Code Blocks
+- `rounded-lg` with glass border
+- Language label top-left (from `data-language` attribute)
+- Copy button top-right on hover
+- `my-6` vertical margins
+
+### Callouts
+- Icons: info=ℹ️, warning=⚠️, tip=💡, danger=🚫
+- `rounded-lg` corners
+- Slightly stronger background tint
+- Better padding and spacing
+
+### Cards
+- Hover: `scale(1.01)` transform + glow shadow
+- Arrow `→` indicator
+- Glass border
+
+### Spacing
+- More generous vertical margins between sections
+- `my-6` on code blocks, `mt-10 mb-4` on headings
+
+## Files to Create/Modify
+
+| File | Change |
+|------|--------|
+| `apps/website/src/components/docs/MdxRenderer.tsx` | Add rehype-pretty-code, update prose styles |
+| `apps/website/src/app/global.css` | Add rehype-pretty-code styles, prose overrides |
+| `apps/website/src/components/docs/DocsTOC.tsx` | NEW — "On This Page" right sidebar |
+| `apps/website/src/components/docs/mdx/Callout.tsx` | Add icons, improve styling |
+| `apps/website/src/components/docs/mdx/Card.tsx` | Add hover effects, arrow |
+| `apps/website/src/components/docs/mdx/Tabs.tsx` | Polish tab bar styling |
+| `apps/website/src/components/docs/mdx/Steps.tsx` | Polish spacing |
+| `apps/website/src/app/docs/[[...slug]]/page.tsx` | Add TOC to layout, pass headings |
+
+## Verification
+- Open /docs/getting-started/quickstart — code blocks have syntax highlighting
+- Code inside Tabs components is highlighted
+- Callouts show icons
+- Cards have hover glow + arrow
+- "On This Page" TOC appears on wide screens
+- Mobile: TOC hidden, content fills width
+- All 19 doc pages render without errors
diff --git a/package-lock.json b/package-lock.json
index 9c333cb38..81a6e33e1 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -27,6 +27,7 @@
         "next-mdx-remote": "^6.0.0",
         "react": "^19.0.0",
         "react-dom": "^19.0.0",
+        "rehype-pretty-code": "^0.14.3",
         "rxjs": "~7.8.0",
         "shiki": "^4.0.2"
       },
@@ -23315,6 +23316,81 @@
         "node": ">= 0.4"
       }
     },
+    "node_modules/hast-util-from-html": {
+      "version": "2.0.3",
+      "resolved": "https://registry.npmjs.org/hast-util-from-html/-/hast-util-from-html-2.0.3.tgz",
+      "integrity": "sha512-CUSRHXyKjzHov8yKsQjGOElXy/3EKpyX56ELnkHH34vDVw1N1XSQ1ZcAvTyAPtGqLTuKP/uxM+aLkSPqF/EtMw==",
+      "license": "MIT",
+      "dependencies": {
+        "@types/hast": "^3.0.0",
+        "devlop": "^1.1.0",
+        "hast-util-from-parse5": "^8.0.0",
+        "parse5": "^7.0.0",
+        "vfile": "^6.0.0",
+        "vfile-message": "^4.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/unified"
+      }
+    },
+    "node_modules/hast-util-from-html/node_modules/entities": {
+      "version": "6.0.1",
+      "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz",
+      "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==",
+      "license": "BSD-2-Clause",
+      "engines": {
+        "node": ">=0.12"
+      },
+      "funding": {
+        "url": "https://github.com/fb55/entities?sponsor=1"
+      }
+    },
+    "node_modules/hast-util-from-html/node_modules/parse5": {
+      "version": "7.3.0",
+      "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz",
+      "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==",
+      "license": "MIT",
+      "dependencies": {
+        "entities": "^6.0.0"
+      },
+      "funding": {
+        "url": "https://github.com/inikulin/parse5?sponsor=1"
+      }
+    },
+    "node_modules/hast-util-from-parse5": {
+      "version": "8.0.3",
+      "resolved": "https://registry.npmjs.org/hast-util-from-parse5/-/hast-util-from-parse5-8.0.3.tgz",
+      "integrity": "sha512-3kxEVkEKt0zvcZ3hCRYI8rqrgwtlIOFMWkbclACvjlDw8Li9S2hk/d51OI0nr/gIpdMHNepwgOKqZ/sy0Clpyg==",
+      "license": "MIT",
+      "dependencies": {
+        "@types/hast": "^3.0.0",
+        "@types/unist": "^3.0.0",
+        "devlop": "^1.0.0",
+        "hastscript": "^9.0.0",
+        "property-information": "^7.0.0",
+        "vfile": "^6.0.0",
+        "vfile-location": "^5.0.0",
+        "web-namespaces": "^2.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/unified"
+      }
+    },
+    "node_modules/hast-util-parse-selector": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-4.0.0.tgz",
+      "integrity": "sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==",
+      "license": "MIT",
+      "dependencies": {
+        "@types/hast": "^3.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/unified"
+      }
+    },
     "node_modules/hast-util-to-estree": {
       "version": "3.1.3",
       "resolved": "https://registry.npmjs.org/hast-util-to-estree/-/hast-util-to-estree-3.1.3.tgz",
@@ -23393,6 +23469,19 @@
         "url": "https://opencollective.com/unified"
       }
     },
+    "node_modules/hast-util-to-string": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/hast-util-to-string/-/hast-util-to-string-3.0.1.tgz",
+      "integrity": "sha512-XelQVTDWvqcl3axRfI0xSeoVKzyIFPwsAGSLIsKdJKQMXDYJS4WYrBNF/8J7RdhIcFI2BOHgAifggsvsxp/3+A==",
+      "license": "MIT",
+      "dependencies": {
+        "@types/hast": "^3.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/unified"
+      }
+    },
     "node_modules/hast-util-whitespace": {
       "version": "3.0.0",
       "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz",
@@ -23406,6 +23495,23 @@
         "url": "https://opencollective.com/unified"
       }
     },
+    "node_modules/hastscript": {
+      "version": "9.0.1",
+      "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-9.0.1.tgz",
+      "integrity": "sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w==",
+      "license": "MIT",
+      "dependencies": {
+        "@types/hast": "^3.0.0",
+        "comma-separated-tokens": "^2.0.0",
+        "hast-util-parse-selector": "^4.0.0",
+        "property-information": "^7.0.0",
+        "space-separated-tokens": "^2.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/unified"
+      }
+    },
     "node_modules/he": {
       "version": "1.2.0",
       "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
@@ -28983,6 +29089,12 @@
         "node": ">= 0.10"
       }
     },
+    "node_modules/parse-numeric-range": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/parse-numeric-range/-/parse-numeric-range-1.3.0.tgz",
+      "integrity": "sha512-twN+njEipszzlMJd4ONUYgSfZPDxgHhT9Ahed5uTigpQn90FggW4SA/AIPq/6a149fTbE9qBEcSwE3FAEp6wQQ==",
+      "license": "ISC"
+    },
     "node_modules/parse-passwd": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz",
@@ -30917,6 +31029,41 @@
         "regjsparser": "bin/parser"
       }
     },
+    "node_modules/rehype-parse": {
+      "version": "9.0.1",
+      "resolved": "https://registry.npmjs.org/rehype-parse/-/rehype-parse-9.0.1.tgz",
+      "integrity": "sha512-ksCzCD0Fgfh7trPDxr2rSylbwq9iYDkSn8TCDmEJ49ljEUBxDVCzCHv7QNzZOfODanX4+bWQ4WZqLCRWYLfhag==",
+      "license": "MIT",
+      "dependencies": {
+        "@types/hast": "^3.0.0",
+        "hast-util-from-html": "^2.0.0",
+        "unified": "^11.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/unified"
+      }
+    },
+    "node_modules/rehype-pretty-code": {
+      "version": "0.14.3",
+      "resolved": "https://registry.npmjs.org/rehype-pretty-code/-/rehype-pretty-code-0.14.3.tgz",
+      "integrity": "sha512-Cz692FeYusTjT5cfFWLc4r7JhgC3/JlJptgUh4iffBxWxUnWW1oqbWFi7jGCeq00DYUm8yzoTnvpocaYa5x82g==",
+      "license": "MIT",
+      "dependencies": {
+        "@types/hast": "^3.0.4",
+        "hast-util-to-string": "^3.0.0",
+        "parse-numeric-range": "^1.3.0",
+        "rehype-parse": "^9.0.0",
+        "unified": "^11.0.5",
+        "unist-util-visit": "^5.0.0"
+      },
+      "engines": {
+        "node": ">=18"
+      },
+      "peerDependencies": {
+        "shiki": "^1.0.0 || ^2.0.0 || ^3.0.0 || ^4.0.0"
+      }
+    },
     "node_modules/rehype-recma": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/rehype-recma/-/rehype-recma-1.0.0.tgz",
@@ -34777,6 +34924,20 @@
         "url": "https://opencollective.com/unified"
       }
     },
+    "node_modules/vfile-location": {
+      "version": "5.0.3",
+      "resolved": "https://registry.npmjs.org/vfile-location/-/vfile-location-5.0.3.tgz",
+      "integrity": "sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg==",
+      "license": "MIT",
+      "dependencies": {
+        "@types/unist": "^3.0.0",
+        "vfile": "^6.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/unified"
+      }
+    },
     "node_modules/vfile-matter": {
       "version": "5.0.1",
       "resolved": "https://registry.npmjs.org/vfile-matter/-/vfile-matter-5.0.1.tgz",
@@ -35043,6 +35204,16 @@
       "license": "MIT",
       "optional": true
     },
+    "node_modules/web-namespaces": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/web-namespaces/-/web-namespaces-2.0.1.tgz",
+      "integrity": "sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==",
+      "license": "MIT",
+      "funding": {
+        "type": "github",
+        "url": "https://github.com/sponsors/wooorm"
+      }
+    },
     "node_modules/webidl-conversions": {
       "version": "3.0.1",
       "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
diff --git a/package.json b/package.json
index f0397a737..302900819 100644
--- a/package.json
+++ b/package.json
@@ -76,6 +76,7 @@
     "next-mdx-remote": "^6.0.0",
     "react": "^19.0.0",
     "react-dom": "^19.0.0",
+    "rehype-pretty-code": "^0.14.3",
     "rxjs": "~7.8.0",
     "shiki": "^4.0.2"
   },
diff --git a/packages/mcp/src/index.js b/packages/mcp/src/index.js
old mode 100644
new mode 100755