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 (
-
-
+
+
+
+
+
+ Docs
+
+
+
+
+
+ API Reference
+
+
+ {activeSpec.label}
+
+
+
+
+
- }
- />
+
+
+
+
+
+
);
}
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;