Skip to content

Commit 830549e

Browse files
committed
docs improvements
1 parent 9d0424e commit 830549e

24 files changed

Lines changed: 257 additions & 283 deletions

apps/docs/app/[lang]/[[...slug]]/page.tsx

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -306,9 +306,12 @@ export async function generateMetadata(props: {
306306
siteName: 'Sim Documentation',
307307
type: 'article',
308308
locale: OG_LOCALE_MAP[lang] ?? 'en_US',
309-
alternateLocale: i18n.languages
310-
.filter((l) => l !== lang)
311-
.map((l) => OG_LOCALE_MAP[l] ?? 'en_US'),
309+
alternateLocale: i18n.languages.reduce<string[]>((locales, l) => {
310+
if (l !== lang) {
311+
locales.push(OG_LOCALE_MAP[l] ?? 'en_US')
312+
}
313+
return locales
314+
}, []),
312315
images: [
313316
{
314317
url: ogImageUrl,

apps/docs/app/[lang]/layout.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
import { Navbar } from '@/components/navbar/navbar'
1212
import { SimLogoFull } from '@/components/ui/sim-logo'
1313
import { i18n } from '@/lib/i18n'
14+
import { serializeJsonLd } from '@/lib/json-ld'
1415
import { source } from '@/lib/source'
1516
import { DOCS_BASE_URL } from '@/lib/urls'
1617
import '../global.css'
@@ -89,7 +90,7 @@ export default async function Layout({ children, params }: LayoutProps) {
8990
<head>
9091
<script
9192
type='application/ld+json'
92-
dangerouslySetInnerHTML={{ __html: JSON.stringify(structuredData) }}
93+
dangerouslySetInnerHTML={{ __html: serializeJsonLd(structuredData) }}
9394
/>
9495
</head>
9596
<body className='flex min-h-screen flex-col font-sans'>

apps/docs/app/api/og/route.tsx

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ const TITLE_FONT_SIZE = {
88
medium: 56,
99
small: 48,
1010
} as const
11+
const FONT_CACHE_REVALIDATE_SECONDS = 60 * 60 * 24 * 30
1112

1213
function getTitleFontSize(title: string): number {
1314
if (title.length > 45) return TITLE_FONT_SIZE.small
@@ -20,12 +21,22 @@ function getTitleFontSize(title: string): number {
2021
*/
2122
async function loadGoogleFont(font: string, weights: string, text: string): Promise<ArrayBuffer> {
2223
const url = `https://fonts.googleapis.com/css2?family=${font}:wght@${weights}&text=${encodeURIComponent(text)}`
23-
const css = await (await fetch(url)).text()
24+
const cssResponse = await fetch(url, {
25+
next: { revalidate: FONT_CACHE_REVALIDATE_SECONDS },
26+
})
27+
28+
if (!cssResponse.ok) {
29+
throw new Error(`Failed to load font CSS: ${cssResponse.status} ${cssResponse.statusText}`)
30+
}
31+
32+
const css = await cssResponse.text()
2433
const resource = css.match(/src: url\((.+)\) format\('(opentype|truetype)'\)/)
2534

2635
if (resource) {
27-
const response = await fetch(resource[1])
28-
if (response.status === 200) {
36+
const response = await fetch(resource[1], {
37+
next: { revalidate: FONT_CACHE_REVALIDATE_SECONDS },
38+
})
39+
if (response.ok) {
2940
return await response.arrayBuffer()
3041
}
3142
}

apps/docs/app/api/search/route.ts

Lines changed: 44 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,20 @@ type SearchRequestBody = {
1414
}
1515

1616
const DEFAULT_SEARCH_LIMIT = 10
17+
const MAX_SEARCH_LIMIT = 20
1718

1819
function getStringParam(value: unknown): string | undefined {
1920
return typeof value === 'string' ? value : undefined
2021
}
2122

2223
function getSearchLimit(value: unknown): number {
2324
const limit = Number.parseInt(String(value ?? DEFAULT_SEARCH_LIMIT), 10)
24-
return Number.isFinite(limit) && limit > 0 ? limit : DEFAULT_SEARCH_LIMIT
25+
26+
if (!Number.isFinite(limit) || limit <= 0) {
27+
return DEFAULT_SEARCH_LIMIT
28+
}
29+
30+
return Math.min(limit, MAX_SEARCH_LIMIT)
2531
}
2632

2733
async function getSearchParams(request: NextRequest) {
@@ -130,6 +136,10 @@ export async function POST(request: NextRequest) {
130136
const keywordRankMap = new Map<string, number>()
131137
keywordResults.forEach((r, idx) => keywordRankMap.set(r.chunkId, idx + 1))
132138

139+
const resultByChunkId = new Map<string, (typeof vectorResults)[number]>()
140+
keywordResults.forEach((result) => resultByChunkId.set(result.chunkId, result))
141+
vectorResults.forEach((result) => resultByChunkId.set(result.chunkId, result))
142+
133143
const allChunkIds = new Set([
134144
...vectorResults.map((r) => r.chunkId),
135145
...keywordResults.map((r) => r.chunkId),
@@ -145,9 +155,7 @@ export async function POST(request: NextRequest) {
145155

146156
const rrfScore = 1 / (k + vectorRank) + 1 / (k + keywordRank)
147157

148-
const result =
149-
vectorResults.find((r) => r.chunkId === chunkId) ||
150-
keywordResults.find((r) => r.chunkId === chunkId)
158+
const result = resultByChunkId.get(chunkId)
151159

152160
if (result) {
153161
scoredResults.push({ ...result, rrfScore })
@@ -203,31 +211,38 @@ export async function POST(request: NextRequest) {
203211
const pathParts = result.sourceDocument
204212
.replace('.mdx', '')
205213
.split('/')
206-
.filter((part) => part !== 'index' && !knownLocales.includes(part))
207-
.map((part) => {
208-
return part
209-
.replace(/_/g, ' ')
210-
.split(' ')
211-
.map((word) => {
212-
const acronyms = [
213-
'api',
214-
'mcp',
215-
'sdk',
216-
'url',
217-
'http',
218-
'json',
219-
'xml',
220-
'html',
221-
'css',
222-
'ai',
223-
]
224-
if (acronyms.includes(word.toLowerCase())) {
225-
return word.toUpperCase()
226-
}
227-
return word.charAt(0).toUpperCase() + word.slice(1)
228-
})
229-
.join(' ')
230-
})
214+
.reduce<string[]>((parts, part) => {
215+
if (part === 'index' || knownLocales.includes(part)) {
216+
return parts
217+
}
218+
219+
parts.push(
220+
part
221+
.replace(/_/g, ' ')
222+
.split(' ')
223+
.map((word) => {
224+
const acronyms = [
225+
'api',
226+
'mcp',
227+
'sdk',
228+
'url',
229+
'http',
230+
'json',
231+
'xml',
232+
'html',
233+
'css',
234+
'ai',
235+
]
236+
if (acronyms.includes(word.toLowerCase())) {
237+
return word.toUpperCase()
238+
}
239+
return word.charAt(0).toUpperCase() + word.slice(1)
240+
})
241+
.join(' ')
242+
)
243+
244+
return parts
245+
}, [])
231246

232247
return {
233248
id: result.chunkId,

apps/docs/components/docs-layout/page-footer.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ export function PageFooter({ previous, next }: PageFooterProps) {
4646
Previous
4747
</span>
4848
<span className='flex items-center gap-1.5 font-[470] text-[rgba(0,0,0,0.7)] text-sm transition-colors group-hover:text-[rgba(0,0,0,0.88)] dark:text-[rgba(255,255,255,0.7)] dark:group-hover:text-[rgba(255,255,255,0.92)]'>
49-
<ChevronLeft className='h-3.5 w-3.5 shrink-0' />
49+
<ChevronLeft className='size-3.5 shrink-0' />
5050
{previous.name}
5151
</span>
5252
</Link>
@@ -68,7 +68,7 @@ export function PageFooter({ previous, next }: PageFooterProps) {
6868
</span>
6969
<span className='flex items-center gap-1.5 font-[470] text-[rgba(0,0,0,0.7)] text-sm transition-colors group-hover:text-[rgba(0,0,0,0.88)] dark:text-[rgba(255,255,255,0.7)] dark:group-hover:text-[rgba(255,255,255,0.92)]'>
7070
{next.name}
71-
<ChevronRight className='h-3.5 w-3.5 shrink-0' />
71+
<ChevronRight className='size-3.5 shrink-0' />
7272
</span>
7373
</Link>
7474
) : (
@@ -90,7 +90,7 @@ export function PageFooter({ previous, next }: PageFooterProps) {
9090
>
9191
<svg
9292
viewBox='0 0 24 24'
93-
className='h-5 w-5 fill-gray-400 transition-colors hover:fill-gray-500 dark:fill-gray-500 dark:hover:fill-gray-400'
93+
className='size-5 fill-neutral-400 transition-colors hover:fill-neutral-500 dark:fill-neutral-500 dark:hover:fill-neutral-400'
9494
>
9595
<path d={link.icon} />
9696
</svg>

apps/docs/components/docs-layout/page-navigation-arrows.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ export function PageNavigationArrows({ previous, next }: PageNavigationArrowsPro
2424
aria-label='Previous page'
2525
title='Previous page'
2626
>
27-
<ChevronLeft className='h-4 w-4' />
27+
<ChevronLeft className='size-4' />
2828
</Link>
2929
)}
3030
{next && (
@@ -34,7 +34,7 @@ export function PageNavigationArrows({ previous, next }: PageNavigationArrowsPro
3434
aria-label='Next page'
3535
title='Next page'
3636
>
37-
<ChevronRight className='h-4 w-4' />
37+
<ChevronRight className='size-4' />
3838
</Link>
3939
)}
4040
</div>

apps/docs/components/page-actions.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,12 @@ export function LLMCopyButton({ content }: { content: string }) {
1414
>
1515
{checked ? (
1616
<>
17-
<Check className='h-3.5 w-3.5' />
17+
<Check className='size-3.5' />
1818
<span>Copied</span>
1919
</>
2020
) : (
2121
<>
22-
<Copy className='h-3.5 w-3.5' />
22+
<Copy className='size-3.5' />
2323
<span>Copy page</span>
2424
</>
2525
)}

apps/docs/components/structured-data.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { serializeJsonLd } from '@/lib/json-ld'
12
import { DOCS_BASE_URL } from '@/lib/urls'
23

34
interface StructuredDataProps {
@@ -103,22 +104,22 @@ export function StructuredData({
103104
<script
104105
type='application/ld+json'
105106
dangerouslySetInnerHTML={{
106-
__html: JSON.stringify(articleStructuredData),
107+
__html: serializeJsonLd(articleStructuredData),
107108
}}
108109
/>
109110
{breadcrumbStructuredData && (
110111
<script
111112
type='application/ld+json'
112113
dangerouslySetInnerHTML={{
113-
__html: JSON.stringify(breadcrumbStructuredData),
114+
__html: serializeJsonLd(breadcrumbStructuredData),
114115
}}
115116
/>
116117
)}
117118
{url === baseUrl && (
118119
<script
119120
type='application/ld+json'
120121
dangerouslySetInnerHTML={{
121-
__html: JSON.stringify(softwareStructuredData),
122+
__html: serializeJsonLd(softwareStructuredData),
122123
}}
123124
/>
124125
)}

apps/docs/components/ui/action-media.tsx

Lines changed: 54 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -19,23 +19,33 @@ interface ActionVideoProps {
1919
export function ActionImage({ src, alt, enableLightbox = true }: ActionImageProps) {
2020
const [isLightboxOpen, setIsLightboxOpen] = useState(false)
2121

22-
const handleClick = () => {
23-
if (enableLightbox) {
24-
setIsLightboxOpen(true)
25-
}
26-
}
22+
const openLightbox = () => setIsLightboxOpen(true)
23+
24+
const image = (
25+
<img
26+
src={src}
27+
alt={alt}
28+
className={cn(
29+
'inline-block w-full max-w-[200px] rounded border border-neutral-200 dark:border-neutral-700',
30+
enableLightbox && 'transition-opacity group-hover:opacity-90'
31+
)}
32+
/>
33+
)
2734

2835
return (
2936
<>
30-
<img
31-
src={src}
32-
alt={alt}
33-
onClick={handleClick}
34-
className={cn(
35-
'inline-block w-full max-w-[200px] rounded border border-neutral-200 dark:border-neutral-700',
36-
enableLightbox && 'cursor-pointer transition-opacity hover:opacity-90'
37-
)}
38-
/>
37+
{enableLightbox ? (
38+
<button
39+
type='button'
40+
onClick={openLightbox}
41+
aria-label={`Open ${alt} in media viewer`}
42+
className='group inline-block cursor-pointer rounded p-0 text-left'
43+
>
44+
{image}
45+
</button>
46+
) : (
47+
image
48+
)}
3949
{enableLightbox && (
4050
<Lightbox
4151
isOpen={isLightboxOpen}
@@ -55,28 +65,40 @@ export function ActionVideo({ src, alt, enableLightbox = true }: ActionVideoProp
5565
const [isLightboxOpen, setIsLightboxOpen] = useState(false)
5666
const resolvedSrc = getAssetUrl(src)
5767

58-
const handleClick = () => {
59-
if (enableLightbox) {
60-
startTimeRef.current = videoRef.current?.currentTime ?? 0
61-
setIsLightboxOpen(true)
62-
}
68+
const openLightbox = () => {
69+
startTimeRef.current = videoRef.current?.currentTime ?? 0
70+
setIsLightboxOpen(true)
6371
}
6472

73+
const video = (
74+
<video
75+
ref={videoRef}
76+
src={resolvedSrc}
77+
autoPlay
78+
loop
79+
muted
80+
playsInline
81+
className={cn(
82+
'inline-block w-full max-w-[200px] rounded border border-neutral-200 dark:border-neutral-700',
83+
enableLightbox && 'transition-opacity group-hover:opacity-90'
84+
)}
85+
/>
86+
)
87+
6588
return (
6689
<>
67-
<video
68-
ref={videoRef}
69-
src={resolvedSrc}
70-
autoPlay
71-
loop
72-
muted
73-
playsInline
74-
onClick={handleClick}
75-
className={cn(
76-
'inline-block w-full max-w-[200px] rounded border border-neutral-200 dark:border-neutral-700',
77-
enableLightbox && 'cursor-pointer transition-opacity hover:opacity-90'
78-
)}
79-
/>
90+
{enableLightbox ? (
91+
<button
92+
type='button'
93+
onClick={openLightbox}
94+
aria-label={`Open ${alt} in media viewer`}
95+
className='group inline-block cursor-pointer rounded p-0 text-left'
96+
>
97+
{video}
98+
</button>
99+
) : (
100+
video
101+
)}
80102
{enableLightbox && (
81103
<Lightbox
82104
isOpen={isLightboxOpen}

apps/docs/components/ui/block-info-card.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ export function BlockInfoCard({
2222
style={{ background: color }}
2323
>
2424
{ResolvedIcon ? (
25-
<ResolvedIcon className='h-10 w-10 text-white' />
25+
<ResolvedIcon className='size-10 text-white' />
2626
) : (
2727
<div className='font-mono text-white text-xl opacity-70'>{type.substring(0, 2)}</div>
2828
)}

0 commit comments

Comments
 (0)