From aaa7d87cf9ed0075a58fe10a437e22915d4a198a Mon Sep 17 00:00:00 2001 From: Thomas Vu Date: Wed, 24 Jun 2026 20:32:12 -0400 Subject: [PATCH] feat(docs): dogfood polish from Boss Raid - Remove Docs eyebrow above sidebar wordmark - Add Agent Skill to documentation tree; drop header nav entry - Fix collection-prefixed internal link resolution (/docs, /dev-docs) - Standalone OpenAPI shell, CharacterNote, lane cards, code block chrome - Agent skill and navigation improvements from monorepo dogfood --- scripts/check-docs.mjs | 8 +- scripts/lib/mdxShortcodes.mjs | 14 +- scripts/lib/seoArtifacts.mjs | 29 +++- shared/documentation-config.js | 6 + src/components/CodeBlock.module.css | 21 +-- src/components/MarkdownRenderer.tsx | 16 ++- src/components/Navigation.tsx | 3 +- src/components/OpenApiSpecSelector.tsx | 18 +-- src/components/docs/DocumentationPage.tsx | 18 +-- src/globals.css | 163 ++++++++++++++-------- src/lib/openapi.ts | 7 +- src/pages/OpenApiPage.tsx | 102 ++++++++++---- src/types/documentation-config.d.ts | 4 +- test/architecture.test.ts | 5 +- themes/default/tokens.css | 8 +- 15 files changed, 277 insertions(+), 145 deletions(-) diff --git a/scripts/check-docs.mjs b/scripts/check-docs.mjs index cb87956..5f762cf 100644 --- a/scripts/check-docs.mjs +++ b/scripts/check-docs.mjs @@ -3,7 +3,12 @@ import { readdir } from 'fs/promises'; import { join } from 'path'; import { getContentCollection } from '../shared/content-collections.js'; -import { documentationTree, frameworkDocPaths, homepageConfig } from '../shared/documentation-config.js'; +import { + documentationTree, + frameworkDocPaths, + homepageConfig, + openapiConfig, +} from '../shared/documentation-config.js'; import { resolveCollectionContentRoot, resolveMonorepoRoot } from './lib/collectionContentRoot.mjs'; import { buildDocsContentPath } from '../shared/docsRouting.js'; import { getDirectoryAliasEntries } from '../shared/seo.js'; @@ -157,6 +162,7 @@ async function main() { const { llmsTxt, llmsFullTxt } = createLlmsArtifacts({ documentationTree, homepageConfig, + openapiConfig, rootDir, }); diff --git a/scripts/lib/mdxShortcodes.mjs b/scripts/lib/mdxShortcodes.mjs index f1a910c..4252b03 100644 --- a/scripts/lib/mdxShortcodes.mjs +++ b/scripts/lib/mdxShortcodes.mjs @@ -159,12 +159,20 @@ function transformCharacterNote(node) { className: 'papers-character-note__avatar', src: characterNoteAvatars[safeType], alt: '', - width: 96, - height: 96, + width: 48, + height: 48, loading: 'lazy', decoding: 'async', }); + const avatarSlot = createElement( + 'div', + { + className: 'papers-character-note__avatar-slot', + }, + [avatar] + ); + const body = createElement( 'div', { @@ -179,7 +187,7 @@ function transformCharacterNote(node) { className: `papers-character-note papers-character-note--${safeType}`, role: 'note', }, - [avatar, body] + [avatarSlot, body] ); } diff --git a/scripts/lib/seoArtifacts.mjs b/scripts/lib/seoArtifacts.mjs index 7df016c..0b30d81 100644 --- a/scripts/lib/seoArtifacts.mjs +++ b/scripts/lib/seoArtifacts.mjs @@ -1,4 +1,4 @@ -import { documentationTree, homepageConfig } from '../../shared/documentation-config.js'; +import { documentationTree, homepageConfig, openapiConfig } from '../../shared/documentation-config.js'; import { buildAbsoluteUrl, DEFAULT_OG_IMAGE_PATH, @@ -233,6 +233,33 @@ export function createAllCollectionSeoRouteEntries(collections, artifactsByColle entries.push(...collectionEntries); } + if (openapiConfig.enabled && openapiConfig.specs.length > 0) { + const routePrefix = openapiConfig.routePrefix || '/api'; + + for (const spec of openapiConfig.specs) { + const routePath = + spec.id === openapiConfig.defaultSpecId + ? routePrefix + : `${routePrefix}/${spec.id}`; + + if (routeMap.has(routePath)) { + continue; + } + + routeMap.set(routePath, true); + entries.push({ + routePath, + canonicalPath: routePath, + title: `${spec.label} | ${options.siteName || getHomeMetadataDefaults().siteName}`, + description: + spec.description || `Interactive OpenAPI reference for ${spec.label}.`, + type: 'article', + includeInSitemap: true, + noIndex: false, + }); + } + } + return entries; } diff --git a/shared/documentation-config.js b/shared/documentation-config.js index bbf59af..8be75ef 100644 --- a/shared/documentation-config.js +++ b/shared/documentation-config.js @@ -343,4 +343,10 @@ export const documentationTree = [ path: 'llms', tags: ['llms', 'ai', 'reference'], }, + { + type: 'file', + name: 'Agent Skill.md', + path: 'skill', + tags: ['skill', 'agent', 'reference'], + }, ]; diff --git a/src/components/CodeBlock.module.css b/src/components/CodeBlock.module.css index f5c3a3f..fc9ff9e 100644 --- a/src/components/CodeBlock.module.css +++ b/src/components/CodeBlock.module.css @@ -1,6 +1,6 @@ /* Theme-driven code block chrome — shape tokens live on each theme's tokens.css */ .codeBlockContainer { - margin: 2rem 0; + margin: 1.5rem 0; border-radius: var(--code-block-radius, 12px); clip-path: var(--code-block-clip-path, none); overflow: hidden; @@ -62,9 +62,9 @@ display: flex; justify-content: space-between; align-items: center; - padding: 0.75rem 1.25rem 0.5rem; - background-color: var(--code-header-bg); - border-bottom: var(--code-block-header-divider, 1px solid var(--code-border-color)); + padding: 0.75rem 1.25rem 0; + background-color: var(--code-bg); + border-bottom: none; } .codeBlockLanguage { @@ -84,9 +84,9 @@ padding: 0.375rem; min-width: 1.75rem; min-height: 1.75rem; - border: 1px solid var(--code-copy-border, var(--code-border-color)); + border: none; border-radius: var(--code-block-radius, 6px); - background-color: var(--code-copy-bg, var(--code-bg)); + background-color: transparent; color: var(--code-copy-text, var(--code-text-color)); font-family: var(--mono-font); font-size: var(--code-label-size); @@ -97,9 +97,10 @@ } .codeBlockCopyBtn:hover { - background-color: var(--code-copy-hover-bg, var(--code-header-bg)); - border-color: var(--code-copy-hover-border, rgba(var(--primary-color-rgb), 0.22)); + background-color: transparent; + border-color: transparent; color: var(--code-copy-text, var(--code-text-color)); + opacity: 0.72; } .codeBlockCopyBtn:focus { @@ -115,9 +116,9 @@ .codeBlockContent { margin: 0; - padding: 0 0 0.25rem; + padding: 0; min-height: calc( - var(--code-block-min-lines, 0) * var(--code-line-height) * var(--code-font-size) + 2.5rem + var(--code-block-min-lines, 0) * var(--code-line-height) * var(--code-font-size) + 1.5rem ); background-color: var(--code-bg); font-family: var(--mono-font) !important; diff --git a/src/components/MarkdownRenderer.tsx b/src/components/MarkdownRenderer.tsx index 64475a4..a418ec2 100644 --- a/src/components/MarkdownRenderer.tsx +++ b/src/components/MarkdownRenderer.tsx @@ -99,8 +99,20 @@ function resolveInternalHref( if (href.startsWith('/')) { const parsed = new URL(href, 'https://docs.local'); - const docPath = parsed.pathname.replace(/^\/+/, ''); - return buildResolvedDocsHref(docPath, `${parsed.search}${parsed.hash}`, context); + const pathname = parsed.pathname.replace(/\/+$/, '') || '/'; + const suffix = `${parsed.search}${parsed.hash}`; + + if ( + pathname === '/docs' || + pathname.startsWith('/docs/') || + pathname === '/dev-docs' || + pathname.startsWith('/dev-docs/') + ) { + return pathname + suffix; + } + + const docPath = pathname.replace(/^\/+/, ''); + return buildResolvedDocsHref(docPath, suffix, context); } const { pathname, suffix } = splitPathSuffix(href); diff --git a/src/components/Navigation.tsx b/src/components/Navigation.tsx index 7978442..f8c756b 100644 --- a/src/components/Navigation.tsx +++ b/src/components/Navigation.tsx @@ -26,7 +26,6 @@ const navItems: NavItem[] = [ { label: 'Design System', href: '/docs/developer-guides/design-system' }, { label: 'Deployment', href: '/docs/deployment/overview' }, { label: 'LLMs.txt', href: '/docs/llms' }, - { label: 'Agent Skill', href: '/docs/skill' }, ]; const bracketAnimationConfig = { @@ -427,7 +426,7 @@ export default function Navigation({ href={link.href} target="_blank" rel="noopener noreferrer" - className={`social-link flex items-center gap-1 ${UI_CLASSES.button}`} + className="social-link flex items-center gap-1" style={{ color: 'var(--muted-color)', }} diff --git a/src/components/OpenApiSpecSelector.tsx b/src/components/OpenApiSpecSelector.tsx index 62648d9..14681a6 100644 --- a/src/components/OpenApiSpecSelector.tsx +++ b/src/components/OpenApiSpecSelector.tsx @@ -1,5 +1,7 @@ import { Link, useLocation } from 'react-router-dom'; +import { buildOpenApiRoutePath } from '../lib/openapi'; + type OpenApiSpecConfig = { id: string; label: string; @@ -11,24 +13,12 @@ type OpenApiSpecSelectorProps = { specs: OpenApiSpecConfig[]; activeSpecId: string; defaultSpecId: string; - pagePath: string; }; -function buildSpecHref(specId: string, defaultSpecId: string, pagePath: string) { - const basePath = `/docs/${pagePath}`; - - if (specId === defaultSpecId) { - return basePath; - } - - return `${basePath}/${specId}`; -} - export default function OpenApiSpecSelector({ specs, activeSpecId, defaultSpecId, - pagePath, }: OpenApiSpecSelectorProps) { const location = useLocation(); @@ -37,9 +27,9 @@ export default function OpenApiSpecSelector({ } return ( -
+
{specs.map((spec) => { - const href = buildSpecHref(spec.id, defaultSpecId, pagePath); + const href = buildOpenApiRoutePath(spec.id === defaultSpecId ? null : spec.id); const isActive = spec.id === activeSpecId; return ( diff --git a/src/components/docs/DocumentationPage.tsx b/src/components/docs/DocumentationPage.tsx index 97f02af..726683a 100644 --- a/src/components/docs/DocumentationPage.tsx +++ b/src/components/docs/DocumentationPage.tsx @@ -377,19 +377,11 @@ const DocumentationPage = React.memo( > P - - - Docs - - - {siteName} - + + {siteName} diff --git a/src/globals.css b/src/globals.css index b9514f2..c6bca2f 100644 --- a/src/globals.css +++ b/src/globals.css @@ -109,7 +109,7 @@ html { --skeleton-base: rgba(17, 17, 17, 0.08); --skeleton-strong: rgba(17, 17, 17, 0.12); --code-bg: #fafafa; - --code-header-bg: #f4f4f5; + --code-header-bg: var(--code-bg); --code-border-color: #e4e4e7; --code-text-color: #111111; --code-muted-color: #71717a; @@ -124,7 +124,7 @@ html { --code-block-radius: 12px; --code-block-clip-path: none; --code-block-border-width: 1px; - --code-block-header-divider: 1px solid var(--code-border-color); + --code-block-header-divider: none; --code-block-shadow: none; --code-copy-bg: var(--code-bg); --code-copy-border: var(--code-border-color); @@ -248,7 +248,7 @@ html { --skeleton-base: rgba(255, 255, 255, 0.08); --skeleton-strong: rgba(255, 255, 255, 0.12); --code-bg: #0f0f10; - --code-header-bg: #151515; + --code-header-bg: var(--code-bg); --code-border-color: #27272a; --code-text-color: #f5f5f5; --code-muted-color: #a1a1aa; @@ -1160,28 +1160,60 @@ code { } .doc-content .papers-character-note { - display: flex; - align-items: flex-start; - gap: var(--space-4); - margin: var(--space-6) 0; - padding: var(--space-4); + display: grid; + grid-template-columns: minmax(0, 15%) minmax(0, 85%); + align-items: center; + gap: var(--space-3); + margin: var(--space-4) 0; + padding: var(--space-3) var(--space-4); border: 1px solid var(--border-unified); - border-left-width: 4px; border-radius: var(--radius-md); background: var(--card-color); } +.doc-content .papers-character-note__avatar-slot { + display: flex; + align-items: center; + justify-content: center; + margin: 0; + padding: 0; + border: none; + background: transparent; + box-shadow: none; +} + .doc-content .papers-character-note__avatar { - width: 6rem; - height: 6rem; - flex-shrink: 0; + width: 100%; + max-width: 100%; + height: auto; + aspect-ratio: 1; object-fit: contain; + margin: 0; + padding: 0; + border: none; + border-radius: 0; + background: none; + box-shadow: none; + outline: none; + display: block; } .doc-content .papers-character-note__body { - flex: 1; + display: flex; + flex-direction: column; + justify-content: center; + align-self: center; min-width: 0; - color: var(--text-color); + color: var(--muted-color); + font-size: var(--text-sm); + line-height: 1.45; +} + +.doc-content .papers-character-note__body p { + margin: 0; + color: inherit; + font-size: inherit; + line-height: inherit; } .doc-content .papers-character-note__body > :first-child { @@ -1199,30 +1231,10 @@ code { font-family: var(--mono-font); } -.doc-content .papers-character-note--info { - border-left-color: var(--info-color); -} - -.doc-content .papers-character-note--warning { - border-left-color: var(--warning-color); -} - -.doc-content .papers-character-note--alert { - border-left-color: var(--error-color); -} - -.doc-content .papers-character-note--success { - border-left-color: var(--success-color); -} - -.doc-content .papers-character-note--tip { - border-left-color: var(--primary-color); -} - @media (max-width: 640px) { - .doc-content .papers-character-note__avatar { - width: 4.5rem; - height: 4.5rem; + .doc-content .papers-character-note { + grid-template-columns: minmax(2.5rem, 15%) minmax(0, 1fr); + gap: var(--space-2); } } @@ -1277,26 +1289,25 @@ code { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); align-items: start; - gap: var(--space-4); - margin: var(--space-6) 0 var(--space-10); + gap: var(--space-5); + margin: var(--space-6) 0 var(--space-12); + padding-bottom: var(--space-2); } .doc-content .role-lane-card { display: flex; flex-direction: column; overflow: visible; - border: 1px solid var(--border-unified); - border-radius: var(--radius-md); - background: var(--card-color); + border: none; + border-radius: 0; + background: transparent; + box-shadow: none; text-decoration: none !important; - transition: - transform var(--transition-slow), - border-color var(--transition-slow); + transition: opacity var(--transition-slow); } .doc-content .role-lane-card:hover { - border-color: var(--primary-color); - transform: translateY(-2px); + opacity: 0.92; } .doc-content .role-lane-card__media { @@ -1317,11 +1328,11 @@ code { } .doc-content .role-lane-card:hover .role-lane-card__media img { - filter: saturate(1) contrast(1.08) brightness(1.03); + filter: saturate(0.88) contrast(1.06); } .doc-content .role-lane-card__body { - padding: var(--space-3) var(--space-4) var(--space-5); + padding: var(--space-3) 0 var(--space-2); } .doc-content .role-lane-card__eyebrow { @@ -1358,14 +1369,41 @@ code { } } +.openapi-page { + height: 100vh; + height: 100dvh; +} + +.openapi-page-main { + min-height: 0; +} + .openapi-reference-shell { - min-height: 70vh; + display: flex; + min-height: 0; + flex: 1; + flex-direction: column; +} + +.openapi-page .openapi-reference-shell { + min-height: 0; } .openapi-reference-shell .scalar-api-reference { + display: flex; + min-height: 0; + flex: 1; + flex-direction: column; + overflow: hidden; +} + +.doc-content .openapi-reference-shell { + min-height: 70vh; +} + +.doc-content .openapi-reference-shell .scalar-api-reference { border: 1px solid var(--border-unified); border-radius: var(--radius-md); - overflow: hidden; } .doc-content .legal-page-header .legal-last-updated { @@ -1640,12 +1678,25 @@ header a:hover, text-decoration: none !important; } -/* Social link hover effects */ -.social-link:hover { +/* Social link hover — tint only, no control chrome */ +.social-link { + padding: 0; + border: none; + border-radius: 0; + background: transparent; + box-shadow: none; +} + +.social-link:hover, +.social-link:focus-visible { + background: transparent !important; + border-color: transparent; + box-shadow: none; color: var(--primary-color) !important; } -.social-link:hover * { +.social-link:hover *, +.social-link:focus-visible * { color: var(--primary-color) !important; } @@ -2270,8 +2321,8 @@ code.wallet-address { } } -/* Add rounded border to all markdown images except icons */ -.markdown-content img:not([src*='pixel-']) { +/* Add rounded border to all markdown images except icons and character-note avatars */ +.markdown-content img:not([src*='pixel-']):not(.papers-character-note__avatar) { border: 1px solid var(--border-color); border-radius: var(--radius-lg); width: 100%; diff --git a/src/lib/openapi.ts b/src/lib/openapi.ts index ca6ce89..843022d 100644 --- a/src/lib/openapi.ts +++ b/src/lib/openapi.ts @@ -1,12 +1,11 @@ import { openapiConfig } from '@app-shared/documentation-config.js'; -export function getOpenApiPagePath() { - return openapiConfig.pagePath || 'api-reference/openapi'; +export function getOpenApiRoutePrefix() { + return openapiConfig.routePrefix || '/api'; } export function buildOpenApiRoutePath(specId?: string | null) { - const pagePath = getOpenApiPagePath(); - const basePath = `/docs/${pagePath}`; + const basePath = getOpenApiRoutePrefix(); if (!specId || specId === openapiConfig.defaultSpecId) { return basePath; diff --git a/src/pages/OpenApiPage.tsx b/src/pages/OpenApiPage.tsx index 38e51e9..43b160b 100644 --- a/src/pages/OpenApiPage.tsx +++ b/src/pages/OpenApiPage.tsx @@ -1,22 +1,19 @@ +import { Icon } from '@iconify/react'; import { useEffect, useMemo } from 'react'; -import { Navigate } from 'react-router-dom'; +import { Link, Navigate, useParams } from 'react-router-dom'; -import DocPageFooter from '../components/DocPageFooter'; -import DocumentationPage from '../components/docs/DocumentationPage'; +import DocsLogoMark from '../components/DocsLogoMark'; import OpenApiReference from '../components/OpenApiReference'; import OpenApiSpecSelector from '../components/OpenApiSpecSelector'; +import ThemeSwitcher from '../components/ThemeSwitcher'; import { openapiConfig } from '../../shared/documentation-config.js'; -import { buildOpenApiRoutePath, getOpenApiPagePath } from '../lib/openapi'; +import { buildOpenApiRoutePath } from '../lib/openapi'; import { applySeoMetadata } from '../utils/seo'; const SITE_NAME = import.meta.env.VITE_SITE_NAME || 'papers'; -type OpenApiPageProps = { - specId?: string; -}; - -export default function OpenApiPage({ specId }: OpenApiPageProps) { - const pagePath = getOpenApiPagePath(); +export default function OpenApiPage() { + const { specId } = useParams(); const specs = openapiConfig.enabled ? openapiConfig.specs : []; const activeSpec = useMemo(() => { const requested = specs.find((spec) => spec.id === specId); @@ -46,6 +43,10 @@ export default function OpenApiPage({ specId }: OpenApiPageProps) { }); }, [activeSpec]); + if (!openapiConfig.enabled) { + return ; + } + if (specId && !activeSpec) { return ; } @@ -55,32 +56,73 @@ export default function OpenApiPage({ specId }: OpenApiPageProps) { } return ( - -
-

- {activeSpec.label} -

-

- {activeSpec.description || 'Interactive OpenAPI explorer.'} Regenerate specs with{' '} - pnpm bossraid sync:openapi. -

-
+
+
+
+ +
+
- - - + + +
- } - /> +
+ +
+ +
+
); } diff --git a/src/types/documentation-config.d.ts b/src/types/documentation-config.d.ts index 4ce883a..f74b4b8 100644 --- a/src/types/documentation-config.d.ts +++ b/src/types/documentation-config.d.ts @@ -75,7 +75,7 @@ declare module '@app-shared/documentation-config.js' { export interface OpenApiConfig { enabled: boolean; - pagePath: string; + routePrefix: string; defaultSpecId: string; specs: OpenApiSpecConfig[]; } @@ -165,7 +165,7 @@ declare module '*/shared/documentation-config.js' { export interface OpenApiConfig { enabled: boolean; - pagePath: string; + routePrefix: string; defaultSpecId: string; specs: OpenApiSpecConfig[]; } diff --git a/test/architecture.test.ts b/test/architecture.test.ts index 6ab98c5..3c6a74a 100644 --- a/test/architecture.test.ts +++ b/test/architecture.test.ts @@ -308,7 +308,7 @@ export const architectureTests: ArchitectureTestCase[] = [ const source = [ '# Character notes', '', - '', + '', 'This route may change.', '', ].join('\n'); @@ -318,9 +318,8 @@ export const architectureTests: ArchitectureTestCase[] = [ assert.match(html, /papers-character-note papers-character-note--warning/); assert.match(html, /papers-character-note__avatar/); assert.match(html, /character-notes\/.*warning/); - assert.match(html, /papers-character-note__title/); - assert.match(html, /Experimental/); assert.match(html, /This route may change/); + assert.doesNotMatch(html, /papers-character-note__title/); }, }, { diff --git a/themes/default/tokens.css b/themes/default/tokens.css index 0b6b890..b70e9a6 100644 --- a/themes/default/tokens.css +++ b/themes/default/tokens.css @@ -60,7 +60,7 @@ --skeleton-base: rgba(17, 17, 17, 0.08); --skeleton-strong: rgba(17, 17, 17, 0.12); --code-bg: #fafafa; - --code-header-bg: #f4f4f5; + --code-header-bg: var(--code-bg); --code-border-color: #e4e4e7; --code-text-color: #111111; --code-muted-color: #71717a; @@ -71,7 +71,7 @@ --code-block-radius: 12px; --code-block-clip-path: none; --code-block-border-width: 1px; - --code-block-header-divider: 1px solid var(--code-border-color); + --code-block-header-divider: none; --code-block-shadow: none; --inline-code-bg: #f4f4f5; --inline-code-border-color: #e4e4e7; @@ -141,7 +141,7 @@ --skeleton-base: rgba(255, 255, 255, 0.08); --skeleton-strong: rgba(255, 255, 255, 0.12); --code-bg: #0f0f10; - --code-header-bg: #151515; + --code-header-bg: var(--code-bg); --code-border-color: #27272a; --code-text-color: #f5f5f5; --code-muted-color: #a1a1aa; @@ -152,7 +152,7 @@ --code-block-radius: 12px; --code-block-clip-path: none; --code-block-border-width: 1px; - --code-block-header-divider: 1px solid var(--code-border-color); + --code-block-header-divider: none; --code-block-shadow: none; --inline-code-bg: #151515; --inline-code-border-color: #27272a;