@btst/stack 1.7.0 → 1.8.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 (67) hide show
  1. package/dist/api/index.d.cts +2 -2
  2. package/dist/api/index.d.mts +2 -2
  3. package/dist/api/index.d.ts +2 -2
  4. package/dist/client/index.cjs +6 -2
  5. package/dist/client/index.d.cts +2 -1
  6. package/dist/client/index.d.mts +2 -1
  7. package/dist/client/index.d.ts +2 -1
  8. package/dist/client/index.mjs +6 -2
  9. package/dist/index.d.cts +1 -1
  10. package/dist/index.d.mts +1 -1
  11. package/dist/index.d.ts +1 -1
  12. package/dist/packages/better-stack/src/plugins/route-docs/client/components/loading/docs-skeleton.cjs +43 -0
  13. package/dist/packages/better-stack/src/plugins/route-docs/client/components/loading/docs-skeleton.mjs +41 -0
  14. package/dist/packages/better-stack/src/plugins/route-docs/client/components/pages/docs-page.cjs +794 -0
  15. package/dist/packages/better-stack/src/plugins/route-docs/client/components/pages/docs-page.mjs +788 -0
  16. package/dist/packages/better-stack/src/plugins/route-docs/client/plugin.cjs +111 -0
  17. package/dist/packages/better-stack/src/plugins/route-docs/client/plugin.mjs +106 -0
  18. package/dist/packages/better-stack/src/plugins/route-docs/generator.cjs +244 -0
  19. package/dist/packages/better-stack/src/plugins/route-docs/generator.mjs +227 -0
  20. package/dist/packages/ui/src/components/sheet.cjs +25 -0
  21. package/dist/packages/ui/src/components/sheet.mjs +24 -1
  22. package/dist/plugins/api/index.d.cts +2 -2
  23. package/dist/plugins/api/index.d.mts +2 -2
  24. package/dist/plugins/api/index.d.ts +2 -2
  25. package/dist/plugins/blog/api/index.d.cts +1 -1
  26. package/dist/plugins/blog/api/index.d.mts +1 -1
  27. package/dist/plugins/blog/api/index.d.ts +1 -1
  28. package/dist/plugins/blog/client/hooks/index.d.cts +2 -2
  29. package/dist/plugins/blog/client/hooks/index.d.mts +2 -2
  30. package/dist/plugins/blog/client/hooks/index.d.ts +2 -2
  31. package/dist/plugins/blog/client/index.d.cts +1 -1
  32. package/dist/plugins/blog/client/index.d.mts +1 -1
  33. package/dist/plugins/blog/client/index.d.ts +1 -1
  34. package/dist/plugins/blog/query-keys.d.cts +2 -2
  35. package/dist/plugins/blog/query-keys.d.mts +2 -2
  36. package/dist/plugins/blog/query-keys.d.ts +2 -2
  37. package/dist/plugins/client/index.d.cts +2 -2
  38. package/dist/plugins/client/index.d.mts +2 -2
  39. package/dist/plugins/client/index.d.ts +2 -2
  40. package/dist/plugins/open-api/api/index.d.cts +1 -1
  41. package/dist/plugins/open-api/api/index.d.mts +1 -1
  42. package/dist/plugins/open-api/api/index.d.ts +1 -1
  43. package/dist/plugins/route-docs/client/index.cjs +10 -0
  44. package/dist/plugins/route-docs/client/index.d.cts +126 -0
  45. package/dist/plugins/route-docs/client/index.d.mts +126 -0
  46. package/dist/plugins/route-docs/client/index.d.ts +126 -0
  47. package/dist/plugins/route-docs/client/index.mjs +1 -0
  48. package/dist/plugins/route-docs/client.css +3 -0
  49. package/dist/plugins/route-docs/style.css +19 -0
  50. package/dist/shared/{stack.CSce37mX.d.cts → stack.u9iYV6vt.d.cts} +14 -2
  51. package/dist/shared/{stack.CSce37mX.d.mts → stack.u9iYV6vt.d.mts} +14 -2
  52. package/dist/shared/{stack.CSce37mX.d.ts → stack.u9iYV6vt.d.ts} +14 -2
  53. package/package.json +15 -1
  54. package/src/client/index.ts +11 -4
  55. package/src/plugins/route-docs/client/components/loading/docs-skeleton.tsx +82 -0
  56. package/src/plugins/route-docs/client/components/loading/index.tsx +1 -0
  57. package/src/plugins/route-docs/client/components/pages/docs-page.tsx +1240 -0
  58. package/src/plugins/route-docs/client/index.ts +7 -0
  59. package/src/plugins/route-docs/client/plugin.tsx +187 -0
  60. package/src/plugins/route-docs/client.css +3 -0
  61. package/src/plugins/route-docs/generator.ts +385 -0
  62. package/src/plugins/route-docs/index.ts +12 -0
  63. package/src/plugins/route-docs/style.css +19 -0
  64. package/src/types.ts +19 -1
  65. package/dist/shared/{stack.CcI4sYJP.d.mts → stack.DLhzx1-D.d.cts} +1 -1
  66. package/dist/shared/{stack.CcI4sYJP.d.ts → stack.DLhzx1-D.d.mts} +1 -1
  67. package/dist/shared/{stack.CcI4sYJP.d.cts → stack.DLhzx1-D.d.ts} +1 -1
@@ -0,0 +1,788 @@
1
+ "use client";
2
+ import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
3
+ import React__default, { useState, useMemo } from 'react';
4
+ import { Card, CardContent, CardHeader, CardTitle } from '../../../../../../../ui/src/components/card.mjs';
5
+ import { Badge } from '../../../../../../../ui/src/components/badge.mjs';
6
+ import { ScrollArea } from '../../../../../../../ui/src/components/scroll-area.mjs';
7
+ import { Separator } from '../../../../../../../ui/src/components/separator.mjs';
8
+ import { Table, TableHeader, TableRow, TableHead, TableBody, TableCell } from '../../../../../../../ui/src/components/table.mjs';
9
+ import { Button } from '../../../../../../../ui/src/components/button.mjs';
10
+ import { Input } from '../../../../../../../ui/src/components/input.mjs';
11
+ import { Label } from '../../../../../../../ui/src/components/label.mjs';
12
+ import { Sheet, SheetTrigger, SheetContent, SheetHeader, SheetTitle } from '../../../../../../../ui/src/components/sheet.mjs';
13
+ import { Menu, Globe, Folder, Link2, ExternalLink, Navigation, FolderOpen, ChevronRight, FileText } from 'lucide-react';
14
+ import { useSuspenseQuery } from '@tanstack/react-query';
15
+ import { generateSchema, ROUTE_DOCS_QUERY_KEY } from '../../plugin.mjs';
16
+
17
+ function escapeRegexForRoutePath(path) {
18
+ const PARAM_PLACEHOLDER = "\0PARAM\0";
19
+ const WILDCARD_PLACEHOLDER = "\0WILDCARD\0";
20
+ let result = path.replace(/:[^/]+/g, PARAM_PLACEHOLDER).replace(/\*/g, WILDCARD_PLACEHOLDER);
21
+ result = result.replace(/[.+?^${}()|[\]\\]/g, "\\$&");
22
+ result = result.replace(new RegExp(PARAM_PLACEHOLDER, "g"), "[^/]+").replace(new RegExp(WILDCARD_PLACEHOLDER, "g"), ".*");
23
+ return result;
24
+ }
25
+ function HighlightedPath({ path }) {
26
+ const parts = path.split("/");
27
+ return /* @__PURE__ */ jsx("code", { className: "font-mono text-xl break-all", children: parts.map((part, i) => {
28
+ const isParam = part.startsWith(":") || part.startsWith("*");
29
+ return /* @__PURE__ */ jsxs(React__default.Fragment, { children: [
30
+ i > 0 && /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "/" }),
31
+ isParam ? /* @__PURE__ */ jsx("span", { className: "text-primary font-semibold", children: part }) : /* @__PURE__ */ jsx("span", { className: "text-foreground", children: part })
32
+ ] }, i);
33
+ }) });
34
+ }
35
+ function ParameterCard({ param }) {
36
+ return /* @__PURE__ */ jsxs("div", { className: "rounded-lg border p-4 space-y-2", children: [
37
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-2 flex-wrap", children: [
38
+ /* @__PURE__ */ jsx("code", { className: "font-mono text-sm text-primary font-semibold", children: param.name }),
39
+ /* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
40
+ /* @__PURE__ */ jsx(Badge, { variant: "secondary", className: "font-mono text-xs", children: param.type }),
41
+ /* @__PURE__ */ jsx(
42
+ Badge,
43
+ {
44
+ variant: param.required ? "destructive" : "outline",
45
+ className: "text-xs",
46
+ children: param.required ? "required" : "optional"
47
+ }
48
+ )
49
+ ] })
50
+ ] }),
51
+ param.description && /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: param.description }),
52
+ param.schema?.enum && /* @__PURE__ */ jsxs("p", { className: "text-xs text-muted-foreground", children: [
53
+ "Values: ",
54
+ param.schema.enum.join(" | ")
55
+ ] })
56
+ ] });
57
+ }
58
+ function ParametersSection({
59
+ params,
60
+ title
61
+ }) {
62
+ if (params.length === 0) return null;
63
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
64
+ /* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold text-muted-foreground uppercase tracking-wide", children: title }),
65
+ /* @__PURE__ */ jsx("div", { className: "hidden md:block rounded-lg border overflow-x-auto", children: /* @__PURE__ */ jsxs(Table, { children: [
66
+ /* @__PURE__ */ jsx(TableHeader, { children: /* @__PURE__ */ jsxs(TableRow, { children: [
67
+ /* @__PURE__ */ jsx(TableHead, { className: "w-[150px]", children: "Name" }),
68
+ /* @__PURE__ */ jsx(TableHead, { className: "w-[120px]", children: "Type" }),
69
+ /* @__PURE__ */ jsx(TableHead, { className: "w-[100px]", children: "Required" }),
70
+ /* @__PURE__ */ jsx(TableHead, { children: "Description" })
71
+ ] }) }),
72
+ /* @__PURE__ */ jsx(TableBody, { children: params.map((param) => /* @__PURE__ */ jsxs(TableRow, { children: [
73
+ /* @__PURE__ */ jsx(TableCell, { children: /* @__PURE__ */ jsx("code", { className: "font-mono text-sm text-primary", children: param.name }) }),
74
+ /* @__PURE__ */ jsxs(TableCell, { children: [
75
+ /* @__PURE__ */ jsx(Badge, { variant: "secondary", className: "font-mono text-xs", children: param.type }),
76
+ param.schema?.enum && /* @__PURE__ */ jsxs("span", { className: "ml-2 text-xs text-muted-foreground", children: [
77
+ "(",
78
+ param.schema.enum.join(" | "),
79
+ ")"
80
+ ] })
81
+ ] }),
82
+ /* @__PURE__ */ jsx(TableCell, { children: /* @__PURE__ */ jsx(
83
+ Badge,
84
+ {
85
+ variant: param.required ? "destructive" : "outline",
86
+ className: "text-xs",
87
+ children: param.required ? "required" : "optional"
88
+ }
89
+ ) }),
90
+ /* @__PURE__ */ jsx(TableCell, { className: "text-muted-foreground", children: param.description || "\u2014" })
91
+ ] }, param.name)) })
92
+ ] }) }),
93
+ /* @__PURE__ */ jsx("div", { className: "md:hidden space-y-3", children: params.map((param) => /* @__PURE__ */ jsx(ParameterCard, { param }, param.name)) })
94
+ ] });
95
+ }
96
+ function NavigationForm({
97
+ route,
98
+ siteBasePath
99
+ }) {
100
+ const [paramValues, setParamValues] = useState({});
101
+ const handleParamChange = (name, value) => {
102
+ setParamValues((prev) => ({ ...prev, [name]: value }));
103
+ };
104
+ const buildUrl = () => {
105
+ let url = route.path;
106
+ for (const param of route.pathParams) {
107
+ const value = paramValues[param.name] || `{${param.name}}`;
108
+ if (param.name === "_") {
109
+ url = url.replace("*", value);
110
+ } else if (url.includes(`*:${param.name}`)) {
111
+ url = url.replace(`*:${param.name}`, value);
112
+ } else {
113
+ url = url.replace(`:${param.name}`, value);
114
+ }
115
+ }
116
+ return `${siteBasePath}${url}`;
117
+ };
118
+ const handleVisit = () => {
119
+ const url = buildUrl();
120
+ const hasUnfilledParams = route.pathParams.some(
121
+ (p) => !paramValues[p.name]
122
+ );
123
+ if (hasUnfilledParams) {
124
+ return;
125
+ }
126
+ window.open(url, "_blank");
127
+ };
128
+ const allParamsFilled = route.pathParams.every((p) => paramValues[p.name]);
129
+ const previewUrl = buildUrl();
130
+ return /* @__PURE__ */ jsxs(Card, { children: [
131
+ /* @__PURE__ */ jsx(CardHeader, { className: "pb-3", children: /* @__PURE__ */ jsxs(CardTitle, { className: "text-base flex items-center gap-2", children: [
132
+ /* @__PURE__ */ jsx(Navigation, { className: "h-4 w-4" }),
133
+ "Navigate to Route"
134
+ ] }) }),
135
+ /* @__PURE__ */ jsx(CardContent, { className: "space-y-4", children: route.pathParams.length > 0 ? /* @__PURE__ */ jsxs(Fragment, { children: [
136
+ /* @__PURE__ */ jsx("div", { className: "grid gap-4 grid-cols-1 sm:grid-cols-2", children: route.pathParams.map((param) => /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
137
+ /* @__PURE__ */ jsxs(Label, { htmlFor: `param-${param.name}`, className: "font-mono", children: [
138
+ ":",
139
+ param.name
140
+ ] }),
141
+ /* @__PURE__ */ jsx(
142
+ Input,
143
+ {
144
+ id: `param-${param.name}`,
145
+ placeholder: `Enter ${param.name}...`,
146
+ value: paramValues[param.name] || "",
147
+ onChange: (e) => handleParamChange(param.name, e.target.value)
148
+ }
149
+ )
150
+ ] }, param.name)) }),
151
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col sm:flex-row items-stretch sm:items-center gap-3 pt-2", children: [
152
+ /* @__PURE__ */ jsx("code", { className: "flex-1 text-xs bg-muted px-3 py-2 rounded-md font-mono text-muted-foreground break-all", children: previewUrl }),
153
+ /* @__PURE__ */ jsxs(
154
+ Button,
155
+ {
156
+ onClick: handleVisit,
157
+ disabled: !allParamsFilled,
158
+ className: "shrink-0",
159
+ children: [
160
+ /* @__PURE__ */ jsx(ExternalLink, { className: "h-4 w-4 mr-2" }),
161
+ "Visit"
162
+ ]
163
+ }
164
+ )
165
+ ] })
166
+ ] }) : /* @__PURE__ */ jsxs("div", { className: "flex flex-col sm:flex-row items-stretch sm:items-center gap-3", children: [
167
+ /* @__PURE__ */ jsxs("code", { className: "flex-1 text-sm bg-muted px-3 py-2 rounded-md font-mono break-all", children: [
168
+ siteBasePath,
169
+ route.path
170
+ ] }),
171
+ /* @__PURE__ */ jsxs(
172
+ Button,
173
+ {
174
+ onClick: () => window.open(`${siteBasePath}${route.path}`, "_blank"),
175
+ className: "shrink-0",
176
+ children: [
177
+ /* @__PURE__ */ jsx(ExternalLink, { className: "h-4 w-4 mr-2" }),
178
+ "Visit"
179
+ ]
180
+ }
181
+ )
182
+ ] }) })
183
+ ] });
184
+ }
185
+ function getMatchingSitemapEntries(route, sitemapEntries) {
186
+ const hasParams = route.pathParams.length > 0;
187
+ if (!hasParams) {
188
+ return sitemapEntries.filter((e) => {
189
+ try {
190
+ const url = new URL(e.url);
191
+ return url.pathname.endsWith(route.path);
192
+ } catch {
193
+ return false;
194
+ }
195
+ });
196
+ } else {
197
+ const routePattern = escapeRegexForRoutePath(route.path);
198
+ const regex = new RegExp(`${routePattern}$`);
199
+ return sitemapEntries.filter((e) => {
200
+ try {
201
+ const url = new URL(e.url);
202
+ return regex.test(url.pathname);
203
+ } catch {
204
+ return false;
205
+ }
206
+ });
207
+ }
208
+ }
209
+ function RouteSitemapSection({
210
+ route,
211
+ sitemapEntries
212
+ }) {
213
+ const matchingEntries = useMemo(
214
+ () => getMatchingSitemapEntries(route, sitemapEntries),
215
+ [route, sitemapEntries]
216
+ );
217
+ if (matchingEntries.length === 0) return null;
218
+ return /* @__PURE__ */ jsxs(Card, { children: [
219
+ /* @__PURE__ */ jsx(CardHeader, { className: "pb-3", children: /* @__PURE__ */ jsxs(CardTitle, { className: "text-base flex items-center gap-2", children: [
220
+ /* @__PURE__ */ jsx(Globe, { className: "h-4 w-4" }),
221
+ "Sitemap Entries",
222
+ /* @__PURE__ */ jsx(Badge, { variant: "secondary", className: "ml-1", children: matchingEntries.length })
223
+ ] }) }),
224
+ /* @__PURE__ */ jsxs(CardContent, { children: [
225
+ /* @__PURE__ */ jsx("div", { className: "hidden md:block rounded-lg border overflow-x-auto", children: /* @__PURE__ */ jsxs(Table, { children: [
226
+ /* @__PURE__ */ jsx(TableHeader, { children: /* @__PURE__ */ jsxs(TableRow, { children: [
227
+ /* @__PURE__ */ jsx(TableHead, { children: "URL" }),
228
+ /* @__PURE__ */ jsx(TableHead, { className: "w-[120px]", children: "Last Modified" }),
229
+ /* @__PURE__ */ jsx(TableHead, { className: "w-[80px]", children: "Priority" }),
230
+ /* @__PURE__ */ jsx(TableHead, { className: "w-[80px]", children: "Actions" })
231
+ ] }) }),
232
+ /* @__PURE__ */ jsx(TableBody, { children: matchingEntries.map((entry, idx) => /* @__PURE__ */ jsxs(TableRow, { children: [
233
+ /* @__PURE__ */ jsx(TableCell, { children: /* @__PURE__ */ jsx(
234
+ "a",
235
+ {
236
+ href: entry.url,
237
+ target: "_blank",
238
+ rel: "noopener noreferrer",
239
+ className: "hover:underline",
240
+ children: /* @__PURE__ */ jsx("code", { className: "font-mono text-xs text-primary truncate block max-w-[400px]", children: entry.url })
241
+ }
242
+ ) }),
243
+ /* @__PURE__ */ jsx(TableCell, { className: "text-xs text-muted-foreground", children: formatDate(entry.lastModified) }),
244
+ /* @__PURE__ */ jsx(TableCell, { className: "text-xs text-muted-foreground", children: entry.priority !== void 0 ? entry.priority : "\u2014" }),
245
+ /* @__PURE__ */ jsx(TableCell, { children: /* @__PURE__ */ jsx(
246
+ Button,
247
+ {
248
+ variant: "ghost",
249
+ size: "sm",
250
+ className: "h-7 px-2",
251
+ onClick: () => window.open(entry.url, "_blank"),
252
+ children: /* @__PURE__ */ jsx(ExternalLink, { className: "h-3 w-3" })
253
+ }
254
+ ) })
255
+ ] }, idx)) })
256
+ ] }) }),
257
+ /* @__PURE__ */ jsx("div", { className: "md:hidden space-y-3", children: matchingEntries.map((entry, idx) => /* @__PURE__ */ jsxs("div", { className: "rounded-lg border p-3 space-y-2", children: [
258
+ /* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between gap-2", children: [
259
+ /* @__PURE__ */ jsx(
260
+ "a",
261
+ {
262
+ href: entry.url,
263
+ target: "_blank",
264
+ rel: "noopener noreferrer",
265
+ className: "font-mono text-xs text-primary break-all hover:underline",
266
+ children: entry.url
267
+ }
268
+ ),
269
+ /* @__PURE__ */ jsx(
270
+ Button,
271
+ {
272
+ variant: "ghost",
273
+ size: "sm",
274
+ className: "h-7 w-7 p-0 shrink-0",
275
+ onClick: () => window.open(entry.url, "_blank"),
276
+ children: /* @__PURE__ */ jsx(ExternalLink, { className: "h-3 w-3" })
277
+ }
278
+ )
279
+ ] }),
280
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap gap-2 text-xs text-muted-foreground", children: [
281
+ entry.lastModified && /* @__PURE__ */ jsx("span", { children: formatDate(entry.lastModified) }),
282
+ entry.priority !== void 0 && /* @__PURE__ */ jsxs("span", { children: [
283
+ "Priority: ",
284
+ entry.priority
285
+ ] })
286
+ ] })
287
+ ] }, idx)) })
288
+ ] })
289
+ ] });
290
+ }
291
+ function RouteDetail({
292
+ route,
293
+ pluginName,
294
+ sitemapEntries,
295
+ siteBasePath
296
+ }) {
297
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
298
+ route.meta && (route.meta.title || route.meta.description) && /* @__PURE__ */ jsxs(Card, { children: [
299
+ /* @__PURE__ */ jsx(CardHeader, { className: "pb-3", children: route.meta.title && /* @__PURE__ */ jsx(CardTitle, { className: "text-lg sm:text-xl", children: route.meta.title }) }),
300
+ (route.meta.description || route.meta.tags && route.meta.tags.length > 0) && /* @__PURE__ */ jsxs(CardContent, { className: "space-y-3", children: [
301
+ route.meta.description && /* @__PURE__ */ jsx("p", { className: "text-muted-foreground text-sm sm:text-base", children: route.meta.description }),
302
+ route.meta.tags && route.meta.tags.length > 0 && /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-2", children: route.meta.tags.map((tag) => /* @__PURE__ */ jsx(Badge, { variant: "secondary", children: tag }, tag)) })
303
+ ] })
304
+ ] }),
305
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col sm:flex-row sm:items-center gap-3 flex-wrap", children: [
306
+ /* @__PURE__ */ jsx("div", { className: "font-mono overflow-x-auto", children: /* @__PURE__ */ jsx(HighlightedPath, { path: route.path }) }),
307
+ /* @__PURE__ */ jsx(Badge, { variant: "outline", children: pluginName })
308
+ ] }),
309
+ /* @__PURE__ */ jsx(NavigationForm, { route, siteBasePath }),
310
+ /* @__PURE__ */ jsx(ParametersSection, { params: route.pathParams, title: "Path Parameters" }),
311
+ /* @__PURE__ */ jsx(ParametersSection, { params: route.queryParams, title: "Query Parameters" }),
312
+ /* @__PURE__ */ jsx(RouteSitemapSection, { route, sitemapEntries })
313
+ ] });
314
+ }
315
+ function getRouteAnchorId(pluginKey, routeKey) {
316
+ return `route-${pluginKey}-${routeKey}`;
317
+ }
318
+ function SidebarRouteItem({
319
+ route,
320
+ pluginKey,
321
+ onNavigate
322
+ }) {
323
+ const anchorId = getRouteAnchorId(pluginKey, route.key);
324
+ const handleClick = (e) => {
325
+ e.preventDefault();
326
+ const element = document.getElementById(anchorId);
327
+ if (element) {
328
+ element.scrollIntoView({ behavior: "smooth", block: "start" });
329
+ window.history.pushState(null, "", `#${anchorId}`);
330
+ }
331
+ onNavigate?.();
332
+ };
333
+ return /* @__PURE__ */ jsxs(
334
+ "a",
335
+ {
336
+ href: `#${anchorId}`,
337
+ onClick: handleClick,
338
+ className: "flex items-center w-full justify-start font-mono text-xs h-auto py-2 px-3 rounded-md hover:bg-accent hover:text-accent-foreground transition-colors",
339
+ children: [
340
+ /* @__PURE__ */ jsx(FileText, { className: "mr-2 h-3 w-3 shrink-0" }),
341
+ /* @__PURE__ */ jsx("span", { className: "truncate", children: route.path })
342
+ ]
343
+ }
344
+ );
345
+ }
346
+ function SidebarPluginGroup({
347
+ plugin,
348
+ onNavigate
349
+ }) {
350
+ const [isExpanded, setIsExpanded] = useState(true);
351
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
352
+ /* @__PURE__ */ jsxs(
353
+ Button,
354
+ {
355
+ variant: "ghost",
356
+ size: "sm",
357
+ className: "w-full justify-between font-medium h-auto py-2",
358
+ onClick: () => setIsExpanded(!isExpanded),
359
+ children: [
360
+ /* @__PURE__ */ jsxs("span", { className: "flex items-center", children: [
361
+ isExpanded ? /* @__PURE__ */ jsx(FolderOpen, { className: "mr-2 h-4 w-4" }) : /* @__PURE__ */ jsx(Folder, { className: "mr-2 h-4 w-4" }),
362
+ plugin.name
363
+ ] }),
364
+ /* @__PURE__ */ jsx(
365
+ ChevronRight,
366
+ {
367
+ className: `h-4 w-4 transition-transform ${isExpanded ? "rotate-90" : ""}`
368
+ }
369
+ )
370
+ ]
371
+ }
372
+ ),
373
+ isExpanded && /* @__PURE__ */ jsx("div", { className: "ml-2 space-y-0.5", children: plugin.routes.map((route) => /* @__PURE__ */ jsx(
374
+ SidebarRouteItem,
375
+ {
376
+ route,
377
+ pluginKey: plugin.key,
378
+ onNavigate
379
+ },
380
+ route.key
381
+ )) })
382
+ ] });
383
+ }
384
+ function SidebarContent({
385
+ schema,
386
+ onNavigate
387
+ }) {
388
+ return /* @__PURE__ */ jsx("div", { className: "p-3 space-y-4", children: schema.plugins.map((plugin) => /* @__PURE__ */ jsx(
389
+ SidebarPluginGroup,
390
+ {
391
+ plugin,
392
+ onNavigate
393
+ },
394
+ plugin.key
395
+ )) });
396
+ }
397
+ function RouteCard({
398
+ pluginName,
399
+ route,
400
+ hasParams,
401
+ staticUrl,
402
+ sitemapCount = 0,
403
+ onSelect
404
+ }) {
405
+ return /* @__PURE__ */ jsxs("div", { className: "rounded-lg border p-4 space-y-3", children: [
406
+ /* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between gap-2", children: [
407
+ /* @__PURE__ */ jsx("button", { onClick: onSelect, className: "text-left hover:underline", children: /* @__PURE__ */ jsx("code", { className: "font-mono text-sm text-primary break-all", children: route.path }) }),
408
+ staticUrl ? /* @__PURE__ */ jsx(
409
+ Button,
410
+ {
411
+ variant: "ghost",
412
+ size: "sm",
413
+ className: "h-8 w-8 p-0 shrink-0",
414
+ onClick: () => window.open(staticUrl, "_blank"),
415
+ children: /* @__PURE__ */ jsx(ExternalLink, { className: "h-4 w-4" })
416
+ }
417
+ ) : /* @__PURE__ */ jsx(
418
+ Button,
419
+ {
420
+ variant: "ghost",
421
+ size: "sm",
422
+ className: "h-8 w-8 p-0 shrink-0",
423
+ onClick: onSelect,
424
+ children: /* @__PURE__ */ jsx(Navigation, { className: "h-4 w-4" })
425
+ }
426
+ )
427
+ ] }),
428
+ route.meta?.title && /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: route.meta.title }),
429
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap gap-2", children: [
430
+ /* @__PURE__ */ jsx(Badge, { variant: "outline", className: "text-xs", children: pluginName }),
431
+ hasParams && /* @__PURE__ */ jsxs(Badge, { variant: "secondary", className: "text-xs", children: [
432
+ route.pathParams.length,
433
+ " param",
434
+ route.pathParams.length > 1 ? "s" : ""
435
+ ] }),
436
+ sitemapCount > 0 && /* @__PURE__ */ jsxs(Badge, { variant: "secondary", className: "text-xs", children: [
437
+ /* @__PURE__ */ jsx(Link2, { className: "h-3 w-3 mr-1" }),
438
+ sitemapCount,
439
+ " in sitemap"
440
+ ] })
441
+ ] })
442
+ ] });
443
+ }
444
+ function formatDate(date) {
445
+ if (!date) return "\u2014";
446
+ const d = typeof date === "string" ? new Date(date) : date;
447
+ return d.toLocaleDateString(void 0, {
448
+ year: "numeric",
449
+ month: "short",
450
+ day: "numeric"
451
+ });
452
+ }
453
+ function SitemapSection({
454
+ entries,
455
+ schema
456
+ }) {
457
+ const [isExpanded, setIsExpanded] = useState(false);
458
+ const getPluginName = (pluginKey) => {
459
+ const plugin = schema.plugins.find((p) => p.key === pluginKey);
460
+ return plugin?.name || pluginKey;
461
+ };
462
+ if (entries.length === 0) return null;
463
+ const displayedEntries = isExpanded ? entries : entries.slice(0, 10);
464
+ const hasMore = entries.length > 10;
465
+ return /* @__PURE__ */ jsxs(Card, { children: [
466
+ /* @__PURE__ */ jsx(CardHeader, { className: "pb-3 sm:pb-6", children: /* @__PURE__ */ jsxs(CardTitle, { className: "text-lg flex items-center gap-2", children: [
467
+ /* @__PURE__ */ jsx(Globe, { className: "h-5 w-5" }),
468
+ "Sitemap Entries",
469
+ /* @__PURE__ */ jsx(Badge, { variant: "secondary", className: "ml-2", children: entries.length })
470
+ ] }) }),
471
+ /* @__PURE__ */ jsxs(CardContent, { children: [
472
+ /* @__PURE__ */ jsx("div", { className: "hidden md:block rounded-lg border overflow-x-auto", children: /* @__PURE__ */ jsxs(Table, { children: [
473
+ /* @__PURE__ */ jsx(TableHeader, { children: /* @__PURE__ */ jsxs(TableRow, { children: [
474
+ /* @__PURE__ */ jsx(TableHead, { children: "URL" }),
475
+ /* @__PURE__ */ jsx(TableHead, { className: "w-[100px]", children: "Plugin" }),
476
+ /* @__PURE__ */ jsx(TableHead, { className: "w-[120px]", children: "Last Modified" }),
477
+ /* @__PURE__ */ jsx(TableHead, { className: "w-[80px]", children: "Actions" })
478
+ ] }) }),
479
+ /* @__PURE__ */ jsx(TableBody, { children: displayedEntries.map((entry, idx) => /* @__PURE__ */ jsxs(TableRow, { children: [
480
+ /* @__PURE__ */ jsx(TableCell, { children: /* @__PURE__ */ jsx(
481
+ "a",
482
+ {
483
+ href: entry.url,
484
+ target: "_blank",
485
+ rel: "noopener noreferrer",
486
+ className: "hover:underline",
487
+ children: /* @__PURE__ */ jsx("code", { className: "font-mono text-xs text-primary truncate block max-w-[400px]", children: entry.url })
488
+ }
489
+ ) }),
490
+ /* @__PURE__ */ jsx(TableCell, { children: /* @__PURE__ */ jsx(Badge, { variant: "outline", className: "text-xs", children: getPluginName(entry.pluginKey) }) }),
491
+ /* @__PURE__ */ jsx(TableCell, { className: "text-xs text-muted-foreground", children: formatDate(entry.lastModified) }),
492
+ /* @__PURE__ */ jsx(TableCell, { children: /* @__PURE__ */ jsx(
493
+ Button,
494
+ {
495
+ variant: "ghost",
496
+ size: "sm",
497
+ className: "h-7 px-2",
498
+ onClick: () => window.open(entry.url, "_blank"),
499
+ children: /* @__PURE__ */ jsx(ExternalLink, { className: "h-3 w-3" })
500
+ }
501
+ ) })
502
+ ] }, `${entry.pluginKey}-${idx}`)) })
503
+ ] }) }),
504
+ /* @__PURE__ */ jsx("div", { className: "md:hidden space-y-3", children: displayedEntries.map((entry, idx) => /* @__PURE__ */ jsxs("div", { className: "rounded-lg border p-3 space-y-2", children: [
505
+ /* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between gap-2", children: [
506
+ /* @__PURE__ */ jsx(
507
+ "a",
508
+ {
509
+ href: entry.url,
510
+ target: "_blank",
511
+ rel: "noopener noreferrer",
512
+ className: "font-mono text-xs text-primary break-all hover:underline",
513
+ children: entry.url
514
+ }
515
+ ),
516
+ /* @__PURE__ */ jsx(
517
+ Button,
518
+ {
519
+ variant: "ghost",
520
+ size: "sm",
521
+ className: "h-7 w-7 p-0 shrink-0",
522
+ onClick: () => window.open(entry.url, "_blank"),
523
+ children: /* @__PURE__ */ jsx(ExternalLink, { className: "h-3 w-3" })
524
+ }
525
+ )
526
+ ] }),
527
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap gap-2 text-xs text-muted-foreground", children: [
528
+ /* @__PURE__ */ jsx(Badge, { variant: "outline", className: "text-xs", children: getPluginName(entry.pluginKey) }),
529
+ entry.lastModified && /* @__PURE__ */ jsx("span", { children: formatDate(entry.lastModified) })
530
+ ] })
531
+ ] }, idx)) }),
532
+ hasMore && /* @__PURE__ */ jsx("div", { className: "mt-4 text-center", children: /* @__PURE__ */ jsx(
533
+ Button,
534
+ {
535
+ variant: "outline",
536
+ size: "sm",
537
+ onClick: () => setIsExpanded(!isExpanded),
538
+ children: isExpanded ? "Show less" : `Show all ${entries.length} entries`
539
+ }
540
+ ) })
541
+ ] })
542
+ ] });
543
+ }
544
+ function AllRoutesSection({
545
+ schema,
546
+ siteBasePath
547
+ }) {
548
+ const scrollToRoute = (pluginKey, routeKey) => {
549
+ const anchorId = getRouteAnchorId(pluginKey, routeKey);
550
+ const element = document.getElementById(anchorId);
551
+ if (element) {
552
+ element.scrollIntoView({ behavior: "smooth", block: "start" });
553
+ window.history.pushState(null, "", `#${anchorId}`);
554
+ }
555
+ };
556
+ const allRoutes = useMemo(() => {
557
+ const routes = [];
558
+ for (const plugin of schema.plugins) {
559
+ for (const route of plugin.routes) {
560
+ const hasParams = route.pathParams.length > 0;
561
+ let sitemapCount = 0;
562
+ if (!hasParams) {
563
+ sitemapCount = plugin.sitemapEntries.filter((e) => {
564
+ try {
565
+ const url = new URL(e.url);
566
+ return url.pathname.endsWith(route.path);
567
+ } catch {
568
+ return false;
569
+ }
570
+ }).length;
571
+ } else {
572
+ const routePattern = escapeRegexForRoutePath(route.path);
573
+ const regex = new RegExp(`${routePattern}$`);
574
+ sitemapCount = plugin.sitemapEntries.filter((e) => {
575
+ try {
576
+ const url = new URL(e.url);
577
+ return regex.test(url.pathname);
578
+ } catch {
579
+ return false;
580
+ }
581
+ }).length;
582
+ }
583
+ routes.push({
584
+ pluginKey: plugin.key,
585
+ pluginName: plugin.name,
586
+ route,
587
+ hasParams,
588
+ staticUrl: hasParams ? null : `${siteBasePath}${route.path}`,
589
+ sitemapCount
590
+ });
591
+ }
592
+ }
593
+ return routes;
594
+ }, [schema, siteBasePath]);
595
+ if (allRoutes.length === 0) return null;
596
+ return /* @__PURE__ */ jsxs(Card, { children: [
597
+ /* @__PURE__ */ jsx(CardHeader, { className: "pb-3 sm:pb-6", children: /* @__PURE__ */ jsx(CardTitle, { className: "text-lg", children: "All Routes" }) }),
598
+ /* @__PURE__ */ jsxs(CardContent, { children: [
599
+ /* @__PURE__ */ jsx("div", { className: "hidden md:block rounded-lg border overflow-x-auto", children: /* @__PURE__ */ jsxs(Table, { children: [
600
+ /* @__PURE__ */ jsx(TableHeader, { children: /* @__PURE__ */ jsxs(TableRow, { children: [
601
+ /* @__PURE__ */ jsx(TableHead, { children: "Route" }),
602
+ /* @__PURE__ */ jsx(TableHead, { className: "w-[100px]", children: "Plugin" }),
603
+ /* @__PURE__ */ jsx(TableHead, { className: "w-[80px]", children: "Params" }),
604
+ /* @__PURE__ */ jsx(TableHead, { className: "w-[80px]", children: "Sitemap" }),
605
+ /* @__PURE__ */ jsx(TableHead, { className: "w-[80px]", children: "Actions" })
606
+ ] }) }),
607
+ /* @__PURE__ */ jsx(TableBody, { children: allRoutes.map(
608
+ ({
609
+ pluginKey,
610
+ pluginName,
611
+ route,
612
+ hasParams,
613
+ staticUrl,
614
+ sitemapCount
615
+ }) => /* @__PURE__ */ jsxs(TableRow, { children: [
616
+ /* @__PURE__ */ jsxs(TableCell, { children: [
617
+ /* @__PURE__ */ jsx(
618
+ "button",
619
+ {
620
+ onClick: () => scrollToRoute(pluginKey, route.key),
621
+ className: "text-left hover:underline",
622
+ children: /* @__PURE__ */ jsx("code", { className: "font-mono text-sm text-primary", children: route.path })
623
+ }
624
+ ),
625
+ route.meta?.title && /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground mt-1", children: route.meta.title })
626
+ ] }),
627
+ /* @__PURE__ */ jsx(TableCell, { children: /* @__PURE__ */ jsx(Badge, { variant: "outline", className: "text-xs", children: pluginName }) }),
628
+ /* @__PURE__ */ jsx(TableCell, { children: hasParams ? /* @__PURE__ */ jsx(Badge, { variant: "secondary", className: "text-xs", children: route.pathParams.length }) : /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: "\u2014" }) }),
629
+ /* @__PURE__ */ jsx(TableCell, { children: sitemapCount > 0 ? /* @__PURE__ */ jsxs(Badge, { variant: "secondary", className: "text-xs", children: [
630
+ /* @__PURE__ */ jsx(Link2, { className: "h-3 w-3 mr-1" }),
631
+ sitemapCount
632
+ ] }) : /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: "\u2014" }) }),
633
+ /* @__PURE__ */ jsx(TableCell, { children: staticUrl ? /* @__PURE__ */ jsx(
634
+ Button,
635
+ {
636
+ variant: "ghost",
637
+ size: "sm",
638
+ className: "h-7 px-2",
639
+ onClick: () => window.open(staticUrl, "_blank"),
640
+ children: /* @__PURE__ */ jsx(ExternalLink, { className: "h-3 w-3" })
641
+ }
642
+ ) : /* @__PURE__ */ jsx(
643
+ Button,
644
+ {
645
+ variant: "ghost",
646
+ size: "sm",
647
+ className: "h-7 px-2",
648
+ onClick: () => scrollToRoute(pluginKey, route.key),
649
+ children: /* @__PURE__ */ jsx(Navigation, { className: "h-3 w-3" })
650
+ }
651
+ ) })
652
+ ] }, `${pluginKey}-${route.key}`)
653
+ ) })
654
+ ] }) }),
655
+ /* @__PURE__ */ jsx("div", { className: "md:hidden space-y-3", children: allRoutes.map(
656
+ ({
657
+ pluginKey,
658
+ pluginName,
659
+ route,
660
+ hasParams,
661
+ staticUrl,
662
+ sitemapCount
663
+ }) => /* @__PURE__ */ jsx(
664
+ RouteCard,
665
+ {
666
+ pluginName,
667
+ route,
668
+ hasParams,
669
+ staticUrl,
670
+ sitemapCount,
671
+ onSelect: () => scrollToRoute(pluginKey, route.key)
672
+ },
673
+ `${pluginKey}-${route.key}`
674
+ )
675
+ ) })
676
+ ] })
677
+ ] });
678
+ }
679
+ function DocsPageComponent({
680
+ title = "Route Documentation",
681
+ description = "Documentation for all client routes in your application",
682
+ siteBasePath = "/pages"
683
+ }) {
684
+ const { data: schema } = useSuspenseQuery({
685
+ queryKey: ROUTE_DOCS_QUERY_KEY,
686
+ queryFn: generateSchema,
687
+ staleTime: Infinity
688
+ // Don't refetch - schema is static for this session
689
+ });
690
+ const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
691
+ const totalRoutes = schema.plugins.reduce(
692
+ (sum, p) => sum + p.routes.length,
693
+ 0
694
+ );
695
+ const handleMobileNavigate = () => {
696
+ setMobileMenuOpen(false);
697
+ };
698
+ return /* @__PURE__ */ jsxs("div", { className: "flex min-h-screen bg-background", children: [
699
+ /* @__PURE__ */ jsxs("aside", { className: "hidden md:block w-72 border-r bg-card shrink-0 sticky top-0 h-screen", children: [
700
+ /* @__PURE__ */ jsx("div", { className: "p-4 border-b", children: /* @__PURE__ */ jsx("h2", { className: "font-semibold text-sm text-muted-foreground uppercase tracking-wide", children: "Routes" }) }),
701
+ /* @__PURE__ */ jsx(ScrollArea, { className: "h-[calc(100vh-57px)]", children: /* @__PURE__ */ jsx(SidebarContent, { schema }) })
702
+ ] }),
703
+ /* @__PURE__ */ jsx("div", { className: "md:hidden fixed top-0 left-0 right-0 z-40 bg-card border-b", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between p-4", children: [
704
+ /* @__PURE__ */ jsx("h2", { className: "font-semibold text-sm text-muted-foreground uppercase tracking-wide", children: "Route Docs" }),
705
+ /* @__PURE__ */ jsxs(Sheet, { open: mobileMenuOpen, onOpenChange: setMobileMenuOpen, children: [
706
+ /* @__PURE__ */ jsx(SheetTrigger, { asChild: true, children: /* @__PURE__ */ jsxs(Button, { variant: "outline", size: "sm", children: [
707
+ /* @__PURE__ */ jsx(Menu, { className: "h-4 w-4 mr-2" }),
708
+ "Routes"
709
+ ] }) }),
710
+ /* @__PURE__ */ jsxs(SheetContent, { side: "left", className: "w-80 p-0", children: [
711
+ /* @__PURE__ */ jsx(SheetHeader, { className: "p-4 border-b", children: /* @__PURE__ */ jsx(SheetTitle, { className: "text-left text-sm text-muted-foreground uppercase tracking-wide", children: "Routes" }) }),
712
+ /* @__PURE__ */ jsx(ScrollArea, { className: "h-[calc(100vh-57px)]", children: /* @__PURE__ */ jsx(
713
+ SidebarContent,
714
+ {
715
+ schema,
716
+ onNavigate: handleMobileNavigate
717
+ }
718
+ ) })
719
+ ] })
720
+ ] })
721
+ ] }) }),
722
+ /* @__PURE__ */ jsx("main", { className: "flex-1 overflow-auto pt-16 md:pt-0", children: /* @__PURE__ */ jsx("div", { className: "max-w-4xl mx-auto p-4 sm:p-6 lg:p-8", children: /* @__PURE__ */ jsxs("div", { className: "space-y-6 sm:space-y-8", children: [
723
+ /* @__PURE__ */ jsxs("div", { children: [
724
+ /* @__PURE__ */ jsx("h1", { className: "text-2xl sm:text-3xl font-bold tracking-tight", children: title }),
725
+ /* @__PURE__ */ jsx("p", { className: "text-muted-foreground mt-2 text-sm sm:text-base", children: description })
726
+ ] }),
727
+ /* @__PURE__ */ jsx(Separator, {}),
728
+ totalRoutes > 0 ? /* @__PURE__ */ jsxs(Fragment, { children: [
729
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap gap-2", children: [
730
+ /* @__PURE__ */ jsxs(Badge, { variant: "secondary", children: [
731
+ schema.plugins.length,
732
+ " plugins"
733
+ ] }),
734
+ /* @__PURE__ */ jsxs(Badge, { variant: "secondary", children: [
735
+ totalRoutes,
736
+ " routes"
737
+ ] }),
738
+ schema.allSitemapEntries.length > 0 && /* @__PURE__ */ jsxs(Badge, { variant: "secondary", children: [
739
+ /* @__PURE__ */ jsx(Globe, { className: "h-3 w-3 mr-1" }),
740
+ schema.allSitemapEntries.length,
741
+ " sitemap entries"
742
+ ] })
743
+ ] }),
744
+ /* @__PURE__ */ jsx(AllRoutesSection, { schema, siteBasePath }),
745
+ schema.plugins.map((plugin) => /* @__PURE__ */ jsxs("div", { className: "space-y-6", children: [
746
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 pt-4", children: [
747
+ /* @__PURE__ */ jsx(Folder, { className: "h-6 w-6 text-muted-foreground" }),
748
+ /* @__PURE__ */ jsx("h2", { className: "text-2xl font-semibold", children: plugin.name }),
749
+ /* @__PURE__ */ jsxs(Badge, { variant: "outline", children: [
750
+ plugin.routes.length,
751
+ " routes"
752
+ ] })
753
+ ] }),
754
+ plugin.routes.map((route) => /* @__PURE__ */ jsx(
755
+ "div",
756
+ {
757
+ id: getRouteAnchorId(plugin.key, route.key),
758
+ className: "scroll-mt-20 md:scroll-mt-4",
759
+ children: /* @__PURE__ */ jsx(
760
+ RouteDetail,
761
+ {
762
+ route,
763
+ pluginName: plugin.name,
764
+ sitemapEntries: plugin.sitemapEntries,
765
+ siteBasePath
766
+ }
767
+ )
768
+ },
769
+ route.key
770
+ )),
771
+ /* @__PURE__ */ jsx(Separator, {})
772
+ ] }, plugin.key)),
773
+ /* @__PURE__ */ jsx(
774
+ SitemapSection,
775
+ {
776
+ entries: schema.allSitemapEntries,
777
+ schema
778
+ }
779
+ )
780
+ ] }) : /* @__PURE__ */ jsx(Card, { children: /* @__PURE__ */ jsxs(CardContent, { className: "py-8 sm:py-12 text-center", children: [
781
+ /* @__PURE__ */ jsx("p", { className: "text-muted-foreground", children: "No documented routes found." }),
782
+ /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground mt-2", children: "Add client plugins with routes to see documentation here." })
783
+ ] }) })
784
+ ] }) }) })
785
+ ] });
786
+ }
787
+
788
+ export { DocsPageComponent };