skrypt-ai 0.3.3 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (97) hide show
  1. package/README.md +1 -1
  2. package/dist/auth/index.d.ts +0 -1
  3. package/dist/auth/index.js +3 -5
  4. package/dist/autofix/index.js +15 -3
  5. package/dist/cli.js +19 -4
  6. package/dist/commands/check-links.js +164 -174
  7. package/dist/commands/deploy.js +5 -2
  8. package/dist/commands/generate.js +206 -199
  9. package/dist/commands/i18n.js +3 -20
  10. package/dist/commands/init.js +47 -40
  11. package/dist/commands/lint.js +3 -20
  12. package/dist/commands/mcp.js +125 -122
  13. package/dist/commands/monitor.js +125 -108
  14. package/dist/commands/review-pr.js +1 -1
  15. package/dist/commands/sdk.js +1 -1
  16. package/dist/config/loader.js +21 -2
  17. package/dist/generator/organizer.d.ts +3 -0
  18. package/dist/generator/organizer.js +4 -9
  19. package/dist/generator/writer.js +2 -10
  20. package/dist/github/pr-comments.js +21 -8
  21. package/dist/plugins/index.js +1 -0
  22. package/dist/scanner/index.js +8 -2
  23. package/dist/template/docs.json +2 -1
  24. package/dist/template/next.config.mjs +3 -1
  25. package/dist/template/package.json +17 -14
  26. package/dist/template/public/favicon.svg +4 -0
  27. package/dist/template/public/search-index.json +1 -1
  28. package/dist/template/scripts/build-search-index.mjs +120 -25
  29. package/dist/template/src/app/api/chat/route.ts +11 -3
  30. package/dist/template/src/app/docs/README.md +28 -0
  31. package/dist/template/src/app/docs/[...slug]/page.tsx +141 -14
  32. package/dist/template/src/app/docs/auth/page.mdx +589 -0
  33. package/dist/template/src/app/docs/autofix/page.mdx +624 -0
  34. package/dist/template/src/app/docs/cli/page.mdx +217 -0
  35. package/dist/template/src/app/docs/config/page.mdx +428 -0
  36. package/dist/template/src/app/docs/configuration/page.mdx +86 -0
  37. package/dist/template/src/app/docs/deployment/page.mdx +112 -0
  38. package/dist/template/src/app/docs/error.tsx +20 -0
  39. package/dist/template/src/app/docs/generator/generator.md +504 -0
  40. package/dist/template/src/app/docs/generator/organizer.md +779 -0
  41. package/dist/template/src/app/docs/generator/page.mdx +613 -0
  42. package/dist/template/src/app/docs/github/page.mdx +502 -0
  43. package/dist/template/src/app/docs/llm/anthropic-client.md +549 -0
  44. package/dist/template/src/app/docs/llm/index.md +471 -0
  45. package/dist/template/src/app/docs/llm/page.mdx +428 -0
  46. package/dist/template/src/app/docs/llms-full.md +256 -0
  47. package/dist/template/src/app/docs/llms.txt +2971 -0
  48. package/dist/template/src/app/docs/not-found.tsx +23 -0
  49. package/dist/template/src/app/docs/page.mdx +0 -3
  50. package/dist/template/src/app/docs/plugins/page.mdx +1793 -0
  51. package/dist/template/src/app/docs/pro/page.mdx +121 -0
  52. package/dist/template/src/app/docs/quickstart/page.mdx +93 -0
  53. package/dist/template/src/app/docs/scanner/content-type.md +599 -0
  54. package/dist/template/src/app/docs/scanner/index.md +212 -0
  55. package/dist/template/src/app/docs/scanner/page.mdx +307 -0
  56. package/dist/template/src/app/docs/scanner/python.md +469 -0
  57. package/dist/template/src/app/docs/scanner/python_parser.md +1056 -0
  58. package/dist/template/src/app/docs/scanner/rust.md +325 -0
  59. package/dist/template/src/app/docs/scanner/typescript.md +201 -0
  60. package/dist/template/src/app/error.tsx +3 -3
  61. package/dist/template/src/app/icon.tsx +29 -0
  62. package/dist/template/src/app/layout.tsx +57 -7
  63. package/dist/template/src/app/not-found.tsx +35 -0
  64. package/dist/template/src/app/page.tsx +95 -11
  65. package/dist/template/src/components/ai-chat.tsx +26 -21
  66. package/dist/template/src/components/breadcrumbs.tsx +56 -12
  67. package/dist/template/src/components/copy-button.tsx +17 -3
  68. package/dist/template/src/components/docs-layout.tsx +202 -8
  69. package/dist/template/src/components/feedback.tsx +4 -2
  70. package/dist/template/src/components/footer.tsx +42 -0
  71. package/dist/template/src/components/header.tsx +56 -20
  72. package/dist/template/src/components/mdx/accordion.tsx +17 -13
  73. package/dist/template/src/components/mdx/callout.tsx +50 -37
  74. package/dist/template/src/components/mdx/card.tsx +24 -12
  75. package/dist/template/src/components/mdx/code-block.tsx +17 -3
  76. package/dist/template/src/components/mdx/code-group.tsx +78 -18
  77. package/dist/template/src/components/mdx/code-playground.tsx +3 -0
  78. package/dist/template/src/components/mdx/go-playground.tsx +3 -0
  79. package/dist/template/src/components/mdx/highlighted-code.tsx +178 -38
  80. package/dist/template/src/components/mdx/python-playground.tsx +2 -0
  81. package/dist/template/src/components/mdx/steps.tsx +6 -6
  82. package/dist/template/src/components/mdx/tabs.tsx +76 -8
  83. package/dist/template/src/components/page-header.tsx +19 -0
  84. package/dist/template/src/components/scroll-to-top.tsx +33 -0
  85. package/dist/template/src/components/search-dialog.tsx +251 -57
  86. package/dist/template/src/components/sidebar.tsx +137 -77
  87. package/dist/template/src/components/table-of-contents.tsx +29 -13
  88. package/dist/template/src/lib/highlight.ts +90 -31
  89. package/dist/template/src/lib/search.ts +14 -4
  90. package/dist/template/src/lib/theme-utils.ts +140 -0
  91. package/dist/template/src/styles/globals.css +397 -84
  92. package/dist/template/src/types/remark-gfm.d.ts +2 -0
  93. package/dist/utils/files.d.ts +9 -0
  94. package/dist/utils/files.js +33 -0
  95. package/dist/utils/validation.d.ts +4 -0
  96. package/dist/utils/validation.js +38 -0
  97. package/package.json +1 -4
@@ -0,0 +1,35 @@
1
+ import Link from 'next/link'
2
+
3
+ export default function NotFound() {
4
+ return (
5
+ <main className="min-h-screen flex flex-col items-center justify-center px-6">
6
+ <div className="text-center max-w-md">
7
+ <p className="text-[5rem] font-bold text-[var(--color-primary)] leading-none mb-4">404</p>
8
+ <h1 className="text-2xl font-bold text-[var(--color-text)] mb-3">
9
+ Page not found
10
+ </h1>
11
+ <p className="text-[var(--color-text-secondary)] mb-8 leading-relaxed">
12
+ The page you are looking for does not exist or may have been moved.
13
+ Try searching the documentation or return to the homepage.
14
+ </p>
15
+ <div className="flex items-center justify-center gap-3">
16
+ <Link
17
+ href="/docs"
18
+ className="inline-flex items-center gap-2 px-5 py-2.5 bg-[var(--color-primary)] text-[var(--color-primary-foreground)] rounded-lg font-medium text-[0.875rem] hover:opacity-90 hover:no-underline transition-opacity"
19
+ >
20
+ Go to Docs
21
+ </Link>
22
+ <Link
23
+ href="/"
24
+ className="inline-flex items-center gap-2 px-5 py-2.5 border border-[var(--color-border)] text-[var(--color-text)] rounded-lg font-medium text-[0.875rem] hover:bg-[var(--color-bg-secondary)] hover:no-underline transition-colors"
25
+ >
26
+ Homepage
27
+ </Link>
28
+ </div>
29
+ <p className="mt-8 text-[0.8125rem] text-[var(--color-text-tertiary)]">
30
+ Tip: Use <kbd className="px-1.5 py-0.5 text-[0.75rem] bg-[var(--color-bg-secondary)] border border-[var(--color-border)] rounded">Ctrl+K</kbd> to search the documentation.
31
+ </p>
32
+ </div>
33
+ </main>
34
+ )
35
+ }
@@ -1,6 +1,7 @@
1
1
  import Link from 'next/link'
2
2
  import { readFileSync } from 'fs'
3
3
  import { join } from 'path'
4
+ import { ArrowRight, BookOpen, Zap, Code } from 'lucide-react'
4
5
 
5
6
  function getDocsConfig() {
6
7
  const configPath = join(process.cwd(), 'docs.json')
@@ -10,19 +11,102 @@ function getDocsConfig() {
10
11
 
11
12
  export default function Home() {
12
13
  const docsConfig = getDocsConfig()
14
+ const firstPage = docsConfig.navigation?.[0]?.pages?.[0]?.path || '/docs'
15
+
16
+ // Collect first 6 pages for quick links
17
+ const allPages: Array<{ title: string; path: string }> = []
18
+ for (const group of docsConfig.navigation || []) {
19
+ for (const page of group.pages || []) {
20
+ if (allPages.length < 6) allPages.push(page)
21
+ }
22
+ }
13
23
 
14
24
  return (
15
- <main className="min-h-screen flex flex-col items-center justify-center p-8">
16
- <h1 className="text-4xl font-bold mb-4">{docsConfig.name} Documentation</h1>
17
- <p className="text-[var(--color-text-secondary)] mb-8 text-center max-w-xl">
18
- {docsConfig.description}
19
- </p>
20
- <Link
21
- href="/docs"
22
- className="px-6 py-3 bg-[var(--color-primary)] text-white rounded-lg font-medium hover:bg-[var(--color-primary-dark)] transition-colors"
23
- >
24
- Get Started
25
- </Link>
25
+ <main id="main-content" className="min-h-screen flex flex-col">
26
+ {/* Header */}
27
+ <header className="h-[var(--header-height)] border-b border-[var(--color-border)] flex items-center px-6">
28
+ <Link href="/" className="font-semibold text-[0.9375rem] tracking-tight text-[var(--color-text)] hover:no-underline">
29
+ {docsConfig.name}
30
+ </Link>
31
+ </header>
32
+
33
+ {/* Hero */}
34
+ <div className="flex-1 flex flex-col items-center justify-center px-6 py-20">
35
+ <div className="max-w-2xl mx-auto text-center relative">
36
+ {/* Gradient glow behind title */}
37
+ <div
38
+ className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-[400px] h-[200px] rounded-full blur-[100px] opacity-20 pointer-events-none"
39
+ style={{ background: `radial-gradient(ellipse, var(--color-primary), transparent 70%)` }}
40
+ />
41
+
42
+ <h1 className="relative text-4xl sm:text-5xl font-bold tracking-tight text-[var(--color-text)] mb-4">
43
+ {docsConfig.name}
44
+ </h1>
45
+ <p className="relative text-lg text-[var(--color-text-secondary)] mb-8 max-w-lg mx-auto leading-relaxed">
46
+ {docsConfig.description}
47
+ </p>
48
+ <div className="relative flex items-center justify-center gap-3">
49
+ <Link
50
+ href={firstPage}
51
+ className="inline-flex items-center gap-2 px-5 py-2.5 bg-[var(--color-primary)] text-[var(--color-primary-foreground)] rounded-lg font-medium text-[0.875rem] hover:opacity-90 hover:no-underline transition-opacity"
52
+ >
53
+ Get Started
54
+ <ArrowRight size={16} />
55
+ </Link>
56
+ <Link
57
+ href="/docs"
58
+ className="inline-flex items-center gap-2 px-5 py-2.5 border border-[var(--color-border)] text-[var(--color-text)] rounded-lg font-medium text-[0.875rem] hover:bg-[var(--color-bg-secondary)] hover:no-underline transition-colors"
59
+ >
60
+ Documentation
61
+ </Link>
62
+ </div>
63
+ </div>
64
+
65
+ {/* Feature cards — show first page title + description from each group */}
66
+ <div className="flex flex-wrap justify-center gap-4 max-w-2xl mx-auto mt-16 w-full">
67
+ {(docsConfig.navigation || []).slice(0, 3).map((group: { group: string; pages: Array<{ title: string; path: string }> }, i: number) => {
68
+ const icons = [BookOpen, Code, Zap]
69
+ const Icon = icons[i % icons.length]
70
+ const firstGroupPage = group.pages?.[0]
71
+ return (
72
+ <Link
73
+ key={group.group}
74
+ href={firstGroupPage?.path || '/docs'}
75
+ className="block p-5 rounded-xl border border-[var(--color-border)] bg-[var(--color-bg)] hover:border-[var(--color-primary)] transition-colors hover:no-underline w-full sm:w-[calc(50%-0.5rem)] min-w-[200px]"
76
+ >
77
+ <Icon size={18} className="text-[var(--color-primary)] mb-3" />
78
+ <h3 className="text-[0.875rem] font-semibold text-[var(--color-text)] mb-1">{group.group}</h3>
79
+ <p className="text-[0.8125rem] text-[var(--color-text-tertiary)] leading-relaxed">
80
+ {firstGroupPage ? `Start with ${firstGroupPage.title}` : `${group.pages.length} pages`}
81
+ </p>
82
+ <p className="text-[0.75rem] text-[var(--color-text-tertiary)] mt-1">
83
+ {group.pages.length} {group.pages.length === 1 ? 'page' : 'pages'}
84
+ </p>
85
+ </Link>
86
+ )
87
+ })}
88
+ </div>
89
+
90
+ {/* Quick Links */}
91
+ {allPages.length > 0 && (
92
+ <div className="max-w-2xl mx-auto mt-10 w-full">
93
+ <p className="text-[0.6875rem] font-semibold uppercase tracking-widest text-[var(--color-text-tertiary)] mb-3 text-center">
94
+ Quick Links
95
+ </p>
96
+ <div className="flex flex-wrap justify-center gap-x-6 gap-y-2">
97
+ {allPages.map((page) => (
98
+ <Link
99
+ key={page.path}
100
+ href={page.path}
101
+ className="text-[0.8125rem] text-[var(--color-text-secondary)] hover:text-[var(--color-primary)] hover:no-underline transition-colors"
102
+ >
103
+ {page.title}
104
+ </Link>
105
+ ))}
106
+ </div>
107
+ </div>
108
+ )}
109
+ </div>
26
110
  </main>
27
111
  )
28
112
  }
@@ -4,6 +4,7 @@ import { useState, useRef, useEffect } from 'react'
4
4
  import { MessageSquare, X, Send, Loader2, ExternalLink } from 'lucide-react'
5
5
 
6
6
  interface Message {
7
+ id: string
7
8
  role: 'user' | 'assistant'
8
9
  content: string
9
10
  citations?: Array<{ title: string; path: string; snippet: string }>
@@ -36,7 +37,7 @@ export function AIChat({
36
37
 
37
38
  const userMessage = input.trim()
38
39
  setInput('')
39
- setMessages(prev => [...prev, { role: 'user', content: userMessage }])
40
+ setMessages(prev => [...prev, { id: Date.now().toString(), role: 'user', content: userMessage }])
40
41
  setIsLoading(true)
41
42
 
42
43
  try {
@@ -56,6 +57,7 @@ export function AIChat({
56
57
  setMessages(prev => [
57
58
  ...prev,
58
59
  {
60
+ id: Date.now().toString(),
59
61
  role: 'assistant',
60
62
  content: data.content,
61
63
  citations: data.citations,
@@ -65,6 +67,7 @@ export function AIChat({
65
67
  setMessages(prev => [
66
68
  ...prev,
67
69
  {
70
+ id: Date.now().toString(),
68
71
  role: 'assistant',
69
72
  content: 'Sorry, I encountered an error. Please try again.',
70
73
  },
@@ -78,7 +81,7 @@ export function AIChat({
78
81
  return (
79
82
  <button
80
83
  onClick={() => setIsOpen(true)}
81
- className="fixed bottom-6 right-6 z-50 flex h-14 w-14 items-center justify-center rounded-full bg-zinc-900 text-white shadow-lg transition-transform hover:scale-105 dark:bg-white dark:text-zinc-900"
84
+ className="fixed bottom-6 right-6 z-50 flex h-14 w-14 items-center justify-center rounded-full bg-[var(--color-text)] text-[var(--color-bg)] shadow-lg transition-transform hover:scale-105"
82
85
  aria-label="Open AI chat"
83
86
  >
84
87
  <MessageSquare className="h-6 w-6" />
@@ -87,21 +90,22 @@ export function AIChat({
87
90
  }
88
91
 
89
92
  return (
90
- <div className="fixed bottom-6 right-6 z-50 flex h-[500px] w-[380px] flex-col overflow-hidden rounded-2xl border border-zinc-200 bg-white shadow-2xl dark:border-zinc-800 dark:bg-zinc-900">
93
+ <div className="fixed bottom-6 right-6 z-50 flex h-[500px] w-[380px] flex-col overflow-hidden rounded-2xl border border-[var(--color-border)] bg-[var(--color-bg)] shadow-2xl">
91
94
  {/* Header */}
92
- <div className="flex items-center justify-between border-b border-zinc-200 px-4 py-3 dark:border-zinc-800">
95
+ <div className="flex items-center justify-between border-b border-[var(--color-border)] px-4 py-3">
93
96
  <div className="flex items-center gap-2">
94
- <div className="flex h-8 w-8 items-center justify-center rounded-lg bg-zinc-900 dark:bg-white">
95
- <MessageSquare className="h-4 w-4 text-white dark:text-zinc-900" />
97
+ <div className="flex h-8 w-8 items-center justify-center rounded-lg bg-[var(--color-text)]">
98
+ <MessageSquare className="h-4 w-4 text-[var(--color-bg)]" />
96
99
  </div>
97
100
  <div>
98
- <p className="text-sm font-medium text-zinc-900 dark:text-white">Ask AI</p>
99
- <p className="text-xs text-zinc-500">About {projectName}</p>
101
+ <p className="text-sm font-medium text-[var(--color-text)]">Ask AI</p>
102
+ <p className="text-xs text-[var(--color-text-tertiary)]">About {projectName}</p>
100
103
  </div>
101
104
  </div>
102
105
  <button
103
106
  onClick={() => setIsOpen(false)}
104
- className="rounded-lg p-1 text-zinc-400 transition-colors hover:bg-zinc-100 hover:text-zinc-600 dark:hover:bg-zinc-800"
107
+ className="rounded-lg p-1 text-[var(--color-text-tertiary)] transition-colors hover:bg-[var(--color-bg-secondary)] hover:text-[var(--color-text-secondary)]"
108
+ aria-label="Close chat"
105
109
  >
106
110
  <X className="h-5 w-5" />
107
111
  </button>
@@ -110,36 +114,36 @@ export function AIChat({
110
114
  {/* Messages */}
111
115
  <div className="flex-1 overflow-y-auto p-4 space-y-4">
112
116
  {messages.length === 0 && (
113
- <div className="text-center text-sm text-zinc-500 py-8">
117
+ <div className="text-center text-sm text-[var(--color-text-tertiary)] py-8">
114
118
  <p>Ask me anything about {projectName}.</p>
115
119
  <p className="mt-2 text-xs">I'll search the docs and give you an answer with sources.</p>
116
120
  </div>
117
121
  )}
118
122
 
119
- {messages.map((message, i) => (
123
+ {messages.map((message) => (
120
124
  <div
121
- key={i}
125
+ key={message.id}
122
126
  className={`flex ${message.role === 'user' ? 'justify-end' : 'justify-start'}`}
123
127
  >
124
128
  <div
125
129
  className={`max-w-[85%] rounded-2xl px-4 py-2 text-sm ${
126
130
  message.role === 'user'
127
- ? 'bg-zinc-900 text-white dark:bg-white dark:text-zinc-900'
128
- : 'bg-zinc-100 text-zinc-900 dark:bg-zinc-800 dark:text-white'
131
+ ? 'bg-[var(--color-text)] text-[var(--color-bg)]'
132
+ : 'bg-[var(--color-bg-tertiary)] text-[var(--color-text)]'
129
133
  }`}
130
134
  >
131
135
  <p className="whitespace-pre-wrap">{message.content}</p>
132
136
 
133
137
  {/* Citations */}
134
138
  {message.citations && message.citations.length > 0 && (
135
- <div className="mt-3 border-t border-zinc-200 pt-2 dark:border-zinc-700">
136
- <p className="text-xs font-medium text-zinc-500 mb-1">Sources:</p>
139
+ <div className="mt-3 border-t border-[var(--color-border)] pt-2">
140
+ <p className="text-xs font-medium text-[var(--color-text-tertiary)] mb-1">Sources:</p>
137
141
  <div className="space-y-1">
138
142
  {message.citations.map((citation, j) => (
139
143
  <a
140
144
  key={j}
141
145
  href={citation.path}
142
- className="flex items-center gap-1 text-xs text-blue-600 hover:underline dark:text-blue-400"
146
+ className="flex items-center gap-1 text-xs text-[var(--color-primary)] hover:underline"
143
147
  >
144
148
  <ExternalLink className="h-3 w-3" />
145
149
  {citation.title}
@@ -154,7 +158,7 @@ export function AIChat({
154
158
 
155
159
  {isLoading && (
156
160
  <div className="flex justify-start">
157
- <div className="flex items-center gap-2 rounded-2xl bg-zinc-100 px-4 py-2 text-sm text-zinc-500 dark:bg-zinc-800">
161
+ <div className="flex items-center gap-2 rounded-2xl bg-[var(--color-bg-tertiary)] px-4 py-2 text-sm text-[var(--color-text-tertiary)]">
158
162
  <Loader2 className="h-4 w-4 animate-spin" />
159
163
  Searching docs...
160
164
  </div>
@@ -167,7 +171,7 @@ export function AIChat({
167
171
  {/* Input */}
168
172
  <form
169
173
  onSubmit={handleSubmit}
170
- className="border-t border-zinc-200 p-3 dark:border-zinc-800"
174
+ className="border-t border-[var(--color-border)] p-3"
171
175
  >
172
176
  <div className="flex items-center gap-2">
173
177
  <input
@@ -176,12 +180,13 @@ export function AIChat({
176
180
  onChange={e => setInput(e.target.value)}
177
181
  placeholder={placeholder}
178
182
  disabled={isLoading}
179
- className="flex-1 rounded-xl border border-zinc-200 bg-zinc-50 px-4 py-2 text-sm outline-none transition-colors placeholder:text-zinc-400 focus:border-zinc-400 dark:border-zinc-700 dark:bg-zinc-800 dark:focus:border-zinc-600"
183
+ className="flex-1 rounded-xl border border-[var(--color-border)] bg-[var(--color-bg-secondary)] px-4 py-2 text-sm text-[var(--color-text)] outline-none transition-colors placeholder:text-[var(--color-text-tertiary)] focus:border-[var(--color-border-strong)]"
180
184
  />
181
185
  <button
182
186
  type="submit"
183
187
  disabled={!input.trim() || isLoading}
184
- className="flex h-10 w-10 items-center justify-center rounded-xl bg-zinc-900 text-white transition-colors hover:bg-zinc-800 disabled:opacity-50 dark:bg-white dark:text-zinc-900 dark:hover:bg-zinc-100"
188
+ className="flex h-10 w-10 items-center justify-center rounded-xl bg-[var(--color-text)] text-[var(--color-bg)] transition-colors hover:opacity-90 disabled:opacity-50"
189
+ aria-label="Send message"
185
190
  >
186
191
  <Send className="h-4 w-4" />
187
192
  </button>
@@ -2,17 +2,61 @@
2
2
 
3
3
  import Link from 'next/link'
4
4
  import { usePathname } from 'next/navigation'
5
- import { ChevronRight, Home } from 'lucide-react'
5
+ import { ChevronRight } from 'lucide-react'
6
6
 
7
- export function Breadcrumbs() {
7
+ interface NavPage { title: string; path: string; pages?: NavPage[] }
8
+ interface NavGroup { group: string; pages: (NavPage | NavGroup)[] }
9
+ interface DocsConfig { navigation: NavGroup[] }
10
+
11
+ function isNavGroup(item: NavPage | NavGroup): item is NavGroup {
12
+ return 'group' in item && !('path' in item)
13
+ }
14
+
15
+ function buildTitleMap(docsConfig?: DocsConfig): Map<string, string> {
16
+ const map = new Map<string, string>()
17
+ if (!docsConfig) return map
18
+
19
+ for (const group of docsConfig.navigation) {
20
+ for (const item of group.pages) {
21
+ if (isNavGroup(item)) {
22
+ fillTitleMap(item.pages, map)
23
+ } else {
24
+ map.set(item.path, item.title)
25
+ if (item.pages) fillTitleMap(item.pages, map)
26
+ }
27
+ }
28
+ }
29
+ return map
30
+ }
31
+
32
+ function fillTitleMap(pages: (NavPage | NavGroup)[], map: Map<string, string>) {
33
+ for (const item of pages) {
34
+ if (isNavGroup(item)) {
35
+ fillTitleMap(item.pages, map)
36
+ } else {
37
+ map.set(item.path, item.title)
38
+ if (item.pages) fillTitleMap(item.pages, map)
39
+ }
40
+ }
41
+ }
42
+
43
+ interface BreadcrumbsProps {
44
+ docsConfig?: DocsConfig
45
+ }
46
+
47
+ export function Breadcrumbs({ docsConfig }: BreadcrumbsProps) {
8
48
  const pathname = usePathname()
9
49
  const segments = pathname.split('/').filter(Boolean)
10
50
 
11
51
  if (segments.length <= 1) return null
12
52
 
53
+ const titleMap = buildTitleMap(docsConfig)
54
+
13
55
  const crumbs = segments.map((segment, index) => {
14
56
  const href = '/' + segments.slice(0, index + 1).join('/')
15
- const label = segment
57
+ // Look up the actual page title from navigation config
58
+ const configTitle = titleMap.get(href)
59
+ const label = configTitle || segment
16
60
  .replace(/-/g, ' ')
17
61
  .replace(/\b\w/g, (c) => c.toUpperCase())
18
62
 
@@ -20,17 +64,17 @@ export function Breadcrumbs() {
20
64
  })
21
65
 
22
66
  return (
23
- <nav className="flex items-center gap-1.5 text-[13px] text-[var(--color-text-tertiary)] mb-4">
24
- <Link href="/" className="hover:text-[var(--color-text)]">
25
- <Home size={14} />
67
+ <nav className="flex items-center gap-1 text-[0.8125rem] text-[var(--color-text-tertiary)] mb-6">
68
+ <Link href="/" className="hover:text-[var(--color-text)] hover:no-underline">
69
+ Docs
26
70
  </Link>
27
- {crumbs.map((crumb, i) => (
28
- <span key={crumb.href} className="flex items-center gap-1.5">
29
- <ChevronRight size={14} />
30
- {i === crumbs.length - 1 ? (
31
- <span className="text-[var(--color-text)]">{crumb.label}</span>
71
+ {crumbs.slice(1).map((crumb, i) => (
72
+ <span key={crumb.href} className="flex items-center gap-1">
73
+ <ChevronRight size={12} className="text-[var(--color-text-tertiary)]" />
74
+ {i === crumbs.length - 2 ? (
75
+ <span className="text-[var(--color-text-secondary)]">{crumb.label}</span>
32
76
  ) : (
33
- <Link href={crumb.href} className="hover:text-[var(--color-text)]">
77
+ <Link href={crumb.href} className="hover:text-[var(--color-text)] hover:no-underline">
34
78
  {crumb.label}
35
79
  </Link>
36
80
  )}
@@ -12,9 +12,23 @@ export function CopyButton({ text, className = '' }: CopyButtonProps) {
12
12
  const [copied, setCopied] = useState(false)
13
13
 
14
14
  async function handleCopy() {
15
- await navigator.clipboard.writeText(text)
16
- setCopied(true)
17
- setTimeout(() => setCopied(false), 2000)
15
+ try {
16
+ await navigator.clipboard.writeText(text)
17
+ setCopied(true)
18
+ setTimeout(() => setCopied(false), 2000)
19
+ } catch {
20
+ // Fallback for insecure contexts
21
+ const textarea = document.createElement('textarea')
22
+ textarea.value = text
23
+ textarea.style.position = 'fixed'
24
+ textarea.style.opacity = '0'
25
+ document.body.appendChild(textarea)
26
+ textarea.select()
27
+ document.execCommand('copy')
28
+ document.body.removeChild(textarea)
29
+ setCopied(true)
30
+ setTimeout(() => setCopied(false), 2000)
31
+ }
18
32
  }
19
33
 
20
34
  return (