@kirchdev/duxt 0.1.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 (401) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +151 -0
  3. package/app/app.config.ts +13 -0
  4. package/app/app.vue +122 -0
  5. package/app/assets/css/duxt.css +555 -0
  6. package/app/assets/css/typeset.css +498 -0
  7. package/app/components/DuxtBrand.vue +53 -0
  8. package/app/components/DuxtBreadcrumb.vue +37 -0
  9. package/app/components/DuxtCodeBlock.vue +143 -0
  10. package/app/components/DuxtCopyPage.vue +117 -0
  11. package/app/components/DuxtFooter.vue +86 -0
  12. package/app/components/DuxtHeader.vue +496 -0
  13. package/app/components/DuxtJsonEditor.vue +359 -0
  14. package/app/components/DuxtLandingProse.vue +90 -0
  15. package/app/components/DuxtLandingShowcase.vue +295 -0
  16. package/app/components/DuxtLiveWindow.vue +343 -0
  17. package/app/components/DuxtLocale.vue +86 -0
  18. package/app/components/DuxtMetaList.vue +76 -0
  19. package/app/components/DuxtNavigation.vue +94 -0
  20. package/app/components/DuxtNavigationLink.vue +41 -0
  21. package/app/components/DuxtOpenApiCallbacks.vue +92 -0
  22. package/app/components/DuxtOpenApiClient.vue +1339 -0
  23. package/app/components/DuxtOpenApiMedia.vue +108 -0
  24. package/app/components/DuxtOpenApiMethod.vue +59 -0
  25. package/app/components/DuxtOpenApiParameters.vue +114 -0
  26. package/app/components/DuxtOpenApiResponses.vue +109 -0
  27. package/app/components/DuxtOpenApiSchema.vue +365 -0
  28. package/app/components/DuxtOpenApiSecurity.vue +115 -0
  29. package/app/components/DuxtPageFeedback.vue +56 -0
  30. package/app/components/DuxtPageInfo.vue +194 -0
  31. package/app/components/DuxtPageNav.vue +93 -0
  32. package/app/components/DuxtPreviewSkeleton.vue +262 -0
  33. package/app/components/DuxtProgress.vue +45 -0
  34. package/app/components/DuxtSearch.vue +271 -0
  35. package/app/components/DuxtSections.vue +65 -0
  36. package/app/components/DuxtShortcuts.vue +46 -0
  37. package/app/components/DuxtSkipLink.vue +34 -0
  38. package/app/components/DuxtToc.vue +86 -0
  39. package/app/components/DuxtTranslationBanner.vue +64 -0
  40. package/app/components/DuxtVersion.vue +168 -0
  41. package/app/components/DuxtVersionBanner.vue +109 -0
  42. package/app/components/OgImage/Duxt.satori.vue +82 -0
  43. package/app/components/content/Accordion.vue +37 -0
  44. package/app/components/content/AccordionItem.vue +46 -0
  45. package/app/components/content/Callout.vue +58 -0
  46. package/app/components/content/ChangelogGroup.vue +91 -0
  47. package/app/components/content/ChangelogReleases.vue +520 -0
  48. package/app/components/content/CodeGroup.vue +183 -0
  49. package/app/components/content/DevtoolsPanel.vue +107 -0
  50. package/app/components/content/Field.vue +50 -0
  51. package/app/components/content/FieldGroup.vue +8 -0
  52. package/app/components/content/FileTree.vue +92 -0
  53. package/app/components/content/Mermaid.vue +70 -0
  54. package/app/components/content/OpenApiOperation.vue +185 -0
  55. package/app/components/content/OpenApiOperations.vue +72 -0
  56. package/app/components/content/OpenApiOverview.vue +302 -0
  57. package/app/components/content/PackageManagers.vue +187 -0
  58. package/app/components/content/PageCards.vue +48 -0
  59. package/app/components/content/Partial.vue +65 -0
  60. package/app/components/content/Preview.vue +173 -0
  61. package/app/components/content/ProseA.vue +55 -0
  62. package/app/components/content/ProseH2.vue +53 -0
  63. package/app/components/content/ProseH3.vue +53 -0
  64. package/app/components/content/ProseH4.vue +53 -0
  65. package/app/components/content/ProseImg.vue +87 -0
  66. package/app/components/content/ProsePre.vue +58 -0
  67. package/app/components/content/ProseTable.vue +11 -0
  68. package/app/components/content/Since.vue +21 -0
  69. package/app/components/content/Steps.vue +20 -0
  70. package/app/components/content/Tab.vue +23 -0
  71. package/app/components/content/TabGroup.vue +58 -0
  72. package/app/components/ui/alert/Alert.vue +21 -0
  73. package/app/components/ui/alert/AlertDescription.vue +22 -0
  74. package/app/components/ui/alert/AlertTitle.vue +22 -0
  75. package/app/components/ui/alert/index.ts +24 -0
  76. package/app/components/ui/badge/Badge.vue +28 -0
  77. package/app/components/ui/badge/index.ts +52 -0
  78. package/app/components/ui/breadcrumb/Breadcrumb.vue +13 -0
  79. package/app/components/ui/breadcrumb/BreadcrumbEllipsis.vue +22 -0
  80. package/app/components/ui/breadcrumb/BreadcrumbItem.vue +17 -0
  81. package/app/components/ui/breadcrumb/BreadcrumbLink.vue +24 -0
  82. package/app/components/ui/breadcrumb/BreadcrumbList.vue +22 -0
  83. package/app/components/ui/breadcrumb/BreadcrumbPage.vue +20 -0
  84. package/app/components/ui/breadcrumb/BreadcrumbSeparator.vue +21 -0
  85. package/app/components/ui/breadcrumb/index.ts +7 -0
  86. package/app/components/ui/button/Button.vue +31 -0
  87. package/app/components/ui/button/index.ts +39 -0
  88. package/app/components/ui/card/Card.vue +22 -0
  89. package/app/components/ui/card/CardAction.vue +22 -0
  90. package/app/components/ui/card/CardContent.vue +14 -0
  91. package/app/components/ui/card/CardDescription.vue +17 -0
  92. package/app/components/ui/card/CardFooter.vue +17 -0
  93. package/app/components/ui/card/CardHeader.vue +22 -0
  94. package/app/components/ui/card/CardTitle.vue +17 -0
  95. package/app/components/ui/card/index.ts +7 -0
  96. package/app/components/ui/collapsible/Collapsible.vue +19 -0
  97. package/app/components/ui/collapsible/CollapsibleContent.vue +12 -0
  98. package/app/components/ui/collapsible/CollapsibleTrigger.vue +12 -0
  99. package/app/components/ui/collapsible/index.ts +3 -0
  100. package/app/components/ui/command/Command.vue +98 -0
  101. package/app/components/ui/command/CommandDialog.vue +42 -0
  102. package/app/components/ui/command/CommandEmpty.vue +31 -0
  103. package/app/components/ui/command/CommandGroup.vue +52 -0
  104. package/app/components/ui/command/CommandInput.vue +45 -0
  105. package/app/components/ui/command/CommandItem.vue +85 -0
  106. package/app/components/ui/command/CommandList.vue +32 -0
  107. package/app/components/ui/command/CommandSeparator.vue +23 -0
  108. package/app/components/ui/command/CommandShortcut.vue +19 -0
  109. package/app/components/ui/command/index.ts +29 -0
  110. package/app/components/ui/dialog/Dialog.vue +15 -0
  111. package/app/components/ui/dialog/DialogClose.vue +12 -0
  112. package/app/components/ui/dialog/DialogContent.vue +61 -0
  113. package/app/components/ui/dialog/DialogDescription.vue +25 -0
  114. package/app/components/ui/dialog/DialogFooter.vue +30 -0
  115. package/app/components/ui/dialog/DialogHeader.vue +17 -0
  116. package/app/components/ui/dialog/DialogOverlay.vue +28 -0
  117. package/app/components/ui/dialog/DialogScrollContent.vue +65 -0
  118. package/app/components/ui/dialog/DialogTitle.vue +25 -0
  119. package/app/components/ui/dialog/DialogTrigger.vue +12 -0
  120. package/app/components/ui/dialog/index.ts +10 -0
  121. package/app/components/ui/dropdown-menu/DropdownMenu.vue +19 -0
  122. package/app/components/ui/dropdown-menu/DropdownMenuCheckboxItem.vue +47 -0
  123. package/app/components/ui/dropdown-menu/DropdownMenuContent.vue +47 -0
  124. package/app/components/ui/dropdown-menu/DropdownMenuGroup.vue +12 -0
  125. package/app/components/ui/dropdown-menu/DropdownMenuItem.vue +41 -0
  126. package/app/components/ui/dropdown-menu/DropdownMenuLabel.vue +27 -0
  127. package/app/components/ui/dropdown-menu/DropdownMenuRadioGroup.vue +21 -0
  128. package/app/components/ui/dropdown-menu/DropdownMenuRadioItem.vue +48 -0
  129. package/app/components/ui/dropdown-menu/DropdownMenuSeparator.vue +23 -0
  130. package/app/components/ui/dropdown-menu/DropdownMenuShortcut.vue +19 -0
  131. package/app/components/ui/dropdown-menu/DropdownMenuSub.vue +19 -0
  132. package/app/components/ui/dropdown-menu/DropdownMenuSubContent.vue +34 -0
  133. package/app/components/ui/dropdown-menu/DropdownMenuSubTrigger.vue +34 -0
  134. package/app/components/ui/dropdown-menu/DropdownMenuTrigger.vue +17 -0
  135. package/app/components/ui/dropdown-menu/index.ts +16 -0
  136. package/app/components/ui/input/Input.vue +35 -0
  137. package/app/components/ui/input/index.ts +1 -0
  138. package/app/components/ui/navigation-menu/NavigationMenu.vue +42 -0
  139. package/app/components/ui/navigation-menu/NavigationMenuContent.vue +35 -0
  140. package/app/components/ui/navigation-menu/NavigationMenuIndicator.vue +32 -0
  141. package/app/components/ui/navigation-menu/NavigationMenuItem.vue +23 -0
  142. package/app/components/ui/navigation-menu/NavigationMenuLink.vue +30 -0
  143. package/app/components/ui/navigation-menu/NavigationMenuList.vue +30 -0
  144. package/app/components/ui/navigation-menu/NavigationMenuTrigger.vue +31 -0
  145. package/app/components/ui/navigation-menu/NavigationMenuViewport.vue +30 -0
  146. package/app/components/ui/navigation-menu/index.ts +14 -0
  147. package/app/components/ui/scroll-area/ScrollArea.vue +31 -0
  148. package/app/components/ui/scroll-area/ScrollBar.vue +38 -0
  149. package/app/components/ui/scroll-area/index.ts +2 -0
  150. package/app/components/ui/select/Select.vue +15 -0
  151. package/app/components/ui/select/SelectContent.vue +59 -0
  152. package/app/components/ui/select/SelectGroup.vue +19 -0
  153. package/app/components/ui/select/SelectItem.vue +42 -0
  154. package/app/components/ui/select/SelectItemText.vue +12 -0
  155. package/app/components/ui/select/SelectLabel.vue +16 -0
  156. package/app/components/ui/select/SelectScrollDownButton.vue +28 -0
  157. package/app/components/ui/select/SelectScrollUpButton.vue +28 -0
  158. package/app/components/ui/select/SelectSeparator.vue +20 -0
  159. package/app/components/ui/select/SelectTrigger.vue +32 -0
  160. package/app/components/ui/select/SelectValue.vue +12 -0
  161. package/app/components/ui/select/index.ts +11 -0
  162. package/app/components/ui/separator/Separator.vue +30 -0
  163. package/app/components/ui/separator/index.ts +1 -0
  164. package/app/components/ui/sheet/Sheet.vue +15 -0
  165. package/app/components/ui/sheet/SheetClose.vue +12 -0
  166. package/app/components/ui/sheet/SheetContent.vue +68 -0
  167. package/app/components/ui/sheet/SheetDescription.vue +23 -0
  168. package/app/components/ui/sheet/SheetFooter.vue +15 -0
  169. package/app/components/ui/sheet/SheetHeader.vue +15 -0
  170. package/app/components/ui/sheet/SheetOverlay.vue +28 -0
  171. package/app/components/ui/sheet/SheetTitle.vue +23 -0
  172. package/app/components/ui/sheet/SheetTrigger.vue +12 -0
  173. package/app/components/ui/sheet/index.ts +8 -0
  174. package/app/components/ui/sidebar/Sidebar.vue +110 -0
  175. package/app/components/ui/sidebar/SidebarContent.vue +23 -0
  176. package/app/components/ui/sidebar/SidebarFooter.vue +18 -0
  177. package/app/components/ui/sidebar/SidebarGroup.vue +18 -0
  178. package/app/components/ui/sidebar/SidebarGroupAction.vue +31 -0
  179. package/app/components/ui/sidebar/SidebarGroupContent.vue +18 -0
  180. package/app/components/ui/sidebar/SidebarGroupLabel.vue +30 -0
  181. package/app/components/ui/sidebar/SidebarHeader.vue +18 -0
  182. package/app/components/ui/sidebar/SidebarInput.vue +19 -0
  183. package/app/components/ui/sidebar/SidebarInset.vue +23 -0
  184. package/app/components/ui/sidebar/SidebarMenu.vue +18 -0
  185. package/app/components/ui/sidebar/SidebarMenuAction.vue +42 -0
  186. package/app/components/ui/sidebar/SidebarMenuBadge.vue +28 -0
  187. package/app/components/ui/sidebar/SidebarMenuButton.vue +60 -0
  188. package/app/components/ui/sidebar/SidebarMenuButtonChild.vue +36 -0
  189. package/app/components/ui/sidebar/SidebarMenuItem.vue +18 -0
  190. package/app/components/ui/sidebar/SidebarMenuSkeleton.vue +35 -0
  191. package/app/components/ui/sidebar/SidebarMenuSub.vue +24 -0
  192. package/app/components/ui/sidebar/SidebarMenuSubButton.vue +43 -0
  193. package/app/components/ui/sidebar/SidebarMenuSubItem.vue +18 -0
  194. package/app/components/ui/sidebar/SidebarProvider.vue +109 -0
  195. package/app/components/ui/sidebar/SidebarRail.vue +35 -0
  196. package/app/components/ui/sidebar/SidebarSeparator.vue +19 -0
  197. package/app/components/ui/sidebar/SidebarTrigger.vue +26 -0
  198. package/app/components/ui/sidebar/index.ts +62 -0
  199. package/app/components/ui/sidebar/utils.ts +19 -0
  200. package/app/components/ui/skeleton/Skeleton.vue +17 -0
  201. package/app/components/ui/skeleton/index.ts +1 -0
  202. package/app/components/ui/sonner/Sonner.vue +67 -0
  203. package/app/components/ui/sonner/index.ts +1 -0
  204. package/app/components/ui/tabs/Tabs.vue +26 -0
  205. package/app/components/ui/tabs/TabsContent.vue +23 -0
  206. package/app/components/ui/tabs/TabsList.vue +28 -0
  207. package/app/components/ui/tabs/TabsTrigger.vue +30 -0
  208. package/app/components/ui/tabs/index.ts +4 -0
  209. package/app/components/ui/textarea/Textarea.vue +32 -0
  210. package/app/components/ui/textarea/index.ts +1 -0
  211. package/app/components/ui/tooltip/Tooltip.vue +15 -0
  212. package/app/components/ui/tooltip/TooltipContent.vue +49 -0
  213. package/app/components/ui/tooltip/TooltipProvider.vue +14 -0
  214. package/app/components/ui/tooltip/TooltipTrigger.vue +12 -0
  215. package/app/components/ui/tooltip/index.ts +4 -0
  216. package/app/composables/useActiveHeading.ts +90 -0
  217. package/app/composables/useDuxtAnimatedHeight.ts +155 -0
  218. package/app/composables/useDuxtBreadcrumb.ts +60 -0
  219. package/app/composables/useDuxtChoice.ts +28 -0
  220. package/app/composables/useDuxtCollection.ts +89 -0
  221. package/app/composables/useDuxtConfig.ts +47 -0
  222. package/app/composables/useDuxtNavigation.ts +87 -0
  223. package/app/composables/useDuxtPageFocus.ts +30 -0
  224. package/app/composables/useDuxtPath.ts +42 -0
  225. package/app/composables/useDuxtSearch.ts +208 -0
  226. package/app/composables/useDuxtSection.ts +44 -0
  227. package/app/composables/useDuxtSectionRow.ts +49 -0
  228. package/app/composables/useDuxtShortcuts.ts +58 -0
  229. package/app/composables/useDuxtSiteUrl.ts +27 -0
  230. package/app/composables/useDuxtToast.ts +25 -0
  231. package/app/composables/useDuxtVersion.ts +138 -0
  232. package/app/composables/useFuzzySearch.ts +83 -0
  233. package/app/composables/usePackageManager.ts +4 -0
  234. package/app/composables/useRecentPages.ts +52 -0
  235. package/app/error.vue +133 -0
  236. package/app/layouts/changelog.vue +85 -0
  237. package/app/layouts/default.vue +23 -0
  238. package/app/layouts/docs.vue +62 -0
  239. package/app/layouts/reference.vue +85 -0
  240. package/app/lib/utils.ts +7 -0
  241. package/app/middleware/duxt-section-layout.global.ts +57 -0
  242. package/app/pages/[...slug].vue +441 -0
  243. package/app/pages/index.vue +479 -0
  244. package/app/types/duxt.d.ts +955 -0
  245. package/app/utils/aside.ts +16 -0
  246. package/app/utils/changelog.ts +182 -0
  247. package/app/utils/code-highlight.ts +116 -0
  248. package/app/utils/duxt-config.ts +185 -0
  249. package/app/utils/duxt-text.ts +135 -0
  250. package/app/utils/file-icons.ts +62 -0
  251. package/app/utils/generated-sections.ts +190 -0
  252. package/app/utils/generated-toc.ts +216 -0
  253. package/app/utils/heading-link.ts +32 -0
  254. package/app/utils/locale-path.ts +45 -0
  255. package/app/utils/navigation-tree.ts +206 -0
  256. package/app/utils/nearest-page.ts +72 -0
  257. package/app/utils/openapi.ts +1028 -0
  258. package/app/utils/package-command.ts +162 -0
  259. package/app/utils/request-samples.ts +635 -0
  260. package/app/utils/section-scope.ts +97 -0
  261. package/app/utils/sheet-navigation.ts +100 -0
  262. package/app/utils/source-file.ts +84 -0
  263. package/app/utils/version-choices.ts +68 -0
  264. package/app/utils/version-paths.ts +65 -0
  265. package/bin/duxt-report.mjs +45 -0
  266. package/components.json +19 -0
  267. package/content-cache.ts +193 -0
  268. package/content.config.ts +38 -0
  269. package/duxt-app-config.ts +124 -0
  270. package/highlight-langs.ts +68 -0
  271. package/i18n/i18n.config.ts +16 -0
  272. package/i18n/locales/de/duxt/changelog.json +17 -0
  273. package/i18n/locales/de/duxt/code.json +13 -0
  274. package/i18n/locales/de/duxt/defaults.json +22 -0
  275. package/i18n/locales/de/duxt/devtools.json +7 -0
  276. package/i18n/locales/de/duxt/error.json +10 -0
  277. package/i18n/locales/de/duxt/footer.json +7 -0
  278. package/i18n/locales/de/duxt/locale.json +7 -0
  279. package/i18n/locales/de/duxt/nav.json +17 -0
  280. package/i18n/locales/de/duxt/openapi.json +89 -0
  281. package/i18n/locales/de/duxt/page.json +34 -0
  282. package/i18n/locales/de/duxt/search.json +12 -0
  283. package/i18n/locales/de/duxt/shortcuts.json +11 -0
  284. package/i18n/locales/de/duxt/theme.json +7 -0
  285. package/i18n/locales/de/duxt/toc.json +7 -0
  286. package/i18n/locales/de/duxt/version.json +25 -0
  287. package/i18n/locales/en/duxt/changelog.json +17 -0
  288. package/i18n/locales/en/duxt/code.json +13 -0
  289. package/i18n/locales/en/duxt/defaults.json +22 -0
  290. package/i18n/locales/en/duxt/devtools.json +7 -0
  291. package/i18n/locales/en/duxt/error.json +10 -0
  292. package/i18n/locales/en/duxt/footer.json +7 -0
  293. package/i18n/locales/en/duxt/locale.json +7 -0
  294. package/i18n/locales/en/duxt/nav.json +17 -0
  295. package/i18n/locales/en/duxt/openapi.json +89 -0
  296. package/i18n/locales/en/duxt/page.json +34 -0
  297. package/i18n/locales/en/duxt/search.json +12 -0
  298. package/i18n/locales/en/duxt/shortcuts.json +11 -0
  299. package/i18n/locales/en/duxt/theme.json +7 -0
  300. package/i18n/locales/en/duxt/toc.json +7 -0
  301. package/i18n/locales/en/duxt/version.json +25 -0
  302. package/i18n/locales/es/duxt/changelog.json +17 -0
  303. package/i18n/locales/es/duxt/code.json +13 -0
  304. package/i18n/locales/es/duxt/defaults.json +22 -0
  305. package/i18n/locales/es/duxt/devtools.json +7 -0
  306. package/i18n/locales/es/duxt/error.json +10 -0
  307. package/i18n/locales/es/duxt/footer.json +7 -0
  308. package/i18n/locales/es/duxt/locale.json +7 -0
  309. package/i18n/locales/es/duxt/nav.json +17 -0
  310. package/i18n/locales/es/duxt/openapi.json +89 -0
  311. package/i18n/locales/es/duxt/page.json +34 -0
  312. package/i18n/locales/es/duxt/search.json +12 -0
  313. package/i18n/locales/es/duxt/shortcuts.json +11 -0
  314. package/i18n/locales/es/duxt/theme.json +7 -0
  315. package/i18n/locales/es/duxt/toc.json +7 -0
  316. package/i18n/locales/es/duxt/version.json +25 -0
  317. package/i18n/locales/fr/duxt/changelog.json +17 -0
  318. package/i18n/locales/fr/duxt/code.json +13 -0
  319. package/i18n/locales/fr/duxt/defaults.json +22 -0
  320. package/i18n/locales/fr/duxt/devtools.json +7 -0
  321. package/i18n/locales/fr/duxt/error.json +10 -0
  322. package/i18n/locales/fr/duxt/footer.json +7 -0
  323. package/i18n/locales/fr/duxt/locale.json +7 -0
  324. package/i18n/locales/fr/duxt/nav.json +17 -0
  325. package/i18n/locales/fr/duxt/openapi.json +89 -0
  326. package/i18n/locales/fr/duxt/page.json +34 -0
  327. package/i18n/locales/fr/duxt/search.json +12 -0
  328. package/i18n/locales/fr/duxt/shortcuts.json +11 -0
  329. package/i18n/locales/fr/duxt/theme.json +7 -0
  330. package/i18n/locales/fr/duxt/toc.json +7 -0
  331. package/i18n/locales/fr/duxt/version.json +25 -0
  332. package/i18n/locales/pt/duxt/changelog.json +17 -0
  333. package/i18n/locales/pt/duxt/code.json +13 -0
  334. package/i18n/locales/pt/duxt/defaults.json +22 -0
  335. package/i18n/locales/pt/duxt/devtools.json +7 -0
  336. package/i18n/locales/pt/duxt/error.json +10 -0
  337. package/i18n/locales/pt/duxt/footer.json +7 -0
  338. package/i18n/locales/pt/duxt/locale.json +7 -0
  339. package/i18n/locales/pt/duxt/nav.json +17 -0
  340. package/i18n/locales/pt/duxt/openapi.json +89 -0
  341. package/i18n/locales/pt/duxt/page.json +34 -0
  342. package/i18n/locales/pt/duxt/search.json +12 -0
  343. package/i18n/locales/pt/duxt/shortcuts.json +11 -0
  344. package/i18n/locales/pt/duxt/theme.json +7 -0
  345. package/i18n/locales/pt/duxt/toc.json +7 -0
  346. package/i18n/locales/pt/duxt/version.json +25 -0
  347. package/i18n/locales/pt-BR/duxt/error.json +8 -0
  348. package/i18n/locales/pt-BR/duxt/nav.json +10 -0
  349. package/i18n/locales/pt-BR/duxt/page.json +10 -0
  350. package/i18n/locales/pt-BR/duxt/search.json +11 -0
  351. package/i18n/locales/pt-BR/duxt/version.json +13 -0
  352. package/mdc.config.ts +28 -0
  353. package/modules/config.ts +440 -0
  354. package/modules/devtools.ts +126 -0
  355. package/modules/git-meta.ts +349 -0
  356. package/modules/redirects.ts +152 -0
  357. package/modules/validate.ts +137 -0
  358. package/nuxt.config.ts +530 -0
  359. package/openapi-model.ts +358 -0
  360. package/openapi-parse.ts +1052 -0
  361. package/package.json +161 -0
  362. package/public/devtools/cache.html +318 -0
  363. package/public/devtools/checks.html +309 -0
  364. package/public/devtools/config.html +461 -0
  365. package/public/devtools/i18n.html +309 -0
  366. package/public/devtools/pages.html +348 -0
  367. package/public/devtools/paths.html +315 -0
  368. package/public/devtools/redirects.html +309 -0
  369. package/public/devtools/search.html +324 -0
  370. package/public/devtools/sources.html +309 -0
  371. package/public/devtools/versions.html +318 -0
  372. package/report.ts +303 -0
  373. package/repository-root.ts +26 -0
  374. package/section-reports.ts +55 -0
  375. package/sections-changelog.ts +644 -0
  376. package/sections-openapi.ts +450 -0
  377. package/sections-resolve.ts +711 -0
  378. package/sections.ts +157 -0
  379. package/server/devtools/content.ts +120 -0
  380. package/server/devtools/context.ts +38 -0
  381. package/server/devtools/entry-path.ts +30 -0
  382. package/server/devtools/handler.ts +104 -0
  383. package/server/devtools/preview.ts +461 -0
  384. package/server/devtools/render/content.ts +233 -0
  385. package/server/devtools/render/sources.ts +414 -0
  386. package/server/devtools/render/system.ts +352 -0
  387. package/server/devtools/shell.ts +611 -0
  388. package/server/devtools/sources.ts +122 -0
  389. package/server/devtools/system.ts +178 -0
  390. package/server/mcp/tools/list-pages.ts +52 -0
  391. package/server/mcp/tools/read-page.ts +57 -0
  392. package/server/mcp/tools/search-docs.ts +74 -0
  393. package/server/middleware/raw-markdown.ts +86 -0
  394. package/server/routes/llms-full.txt.get.ts +75 -0
  395. package/server/routes/llms.txt.get.ts +63 -0
  396. package/server/routes/rss.xml.get.ts +125 -0
  397. package/server/utils/duxt-server-text.ts +46 -0
  398. package/sources-git.ts +113 -0
  399. package/sources-resolve.ts +908 -0
  400. package/sources.ts +287 -0
  401. package/validate-report.ts +494 -0
@@ -0,0 +1,168 @@
1
+ <script setup lang="ts">
2
+ /**
3
+ * The one place a version is shown.
4
+ *
5
+ * Before, the project's version sat as a badge beside the title while the
6
+ * documentation's versions lived in a separate dropdown on the far right —
7
+ * two controls saying different things, and the right-hand one appeared only
8
+ * on sources that have versions, so the icons beside it shifted as you moved
9
+ * between repositories.
10
+ *
11
+ * Now it is one element next to the title: a badge when there is nothing to
12
+ * choose, the same badge as a trigger when there is.
13
+ */
14
+
15
+ /**
16
+ * `badge` is the header's inline pill. `block` is the mobile sheet's: a
17
+ * full-width control with its own caption, because in a column of navigation
18
+ * rows a bare pill reads as a label rather than as something to press.
19
+ */
20
+ const props = withDefaults(defineProps<{ variant?: 'badge' | 'block' }>(), {
21
+ variant: 'badge'
22
+ });
23
+
24
+ const duxt = useDuxtConfig();
25
+ const path = useDuxtPath();
26
+ const localeLink = useDuxtLink();
27
+
28
+ /**
29
+ * Versions come from the resolved source manifest, so the control can only
30
+ * offer what has a collection behind it, and only for the repository being
31
+ * read — one project's versions say nothing about another's. The list itself
32
+ * is `useDuxtVersion`'s, because the sheet asks for it too — see
33
+ * `versionChoices`.
34
+ */
35
+ const { choices: versions } = useDuxtVersion();
36
+
37
+ const current = computed(() =>
38
+ sourceForPath(
39
+ path.value,
40
+ versions.value.map((version) => ({ ...version, prefix: version.to ?? '' }))
41
+ )
42
+ );
43
+
44
+ const { t, te } = useI18n();
45
+
46
+ /**
47
+ * The caption beside an entry.
48
+ *
49
+ * The list carries a translation KEY where the layer wrote one
50
+ * (`duxt.version.status.deprecated`) and a plain word where a consumer did, so
51
+ * a key that resolves is translated and anything else is shown as written.
52
+ * `asText` because the config type keeps `DuxtText` even though
53
+ * `useDuxtConfig` has already collapsed it to a string.
54
+ */
55
+ function caption(version: DuxtLink) {
56
+ const value = asText(version.description);
57
+ if (!value) return undefined;
58
+
59
+ return te(value) ? t(value) : value;
60
+ }
61
+
62
+ /** Same page, other version: swap the prefix rather than jumping to its root. */
63
+ function pathIn(version: { to?: string }) {
64
+ return localeLink(
65
+ versionPath(path.value, current.value?.to, version.to ?? '/')
66
+ );
67
+ }
68
+ </script>
69
+
70
+ <template>
71
+ <UiDropdownMenu v-if="versions.length > 1">
72
+ <!-- The trigger is a real <button>, with the Badge inside it. reka-ui puts
73
+ `aria-haspopup` and `aria-expanded` on whatever element it is handed,
74
+ and a <span> is allowed neither — which axe reports and a screen
75
+ reader acts on. -->
76
+ <UiDropdownMenuTrigger as-child>
77
+ <!-- Built like shadcn's own large sidebar button: a square mark, then
78
+ two lines, then the chevrons. The VERSION LEADS and the caption sits
79
+ under it — the reverse of what this was, where the reader met the
80
+ word "Version" before the number they came to read. The caption is
81
+ the version's own lifecycle where it has one ("default",
82
+ "maintenance"), because on a switcher that is the fact worth the
83
+ second line; the label is what is left when it has none. -->
84
+ <button
85
+ v-if="props.variant === 'block'"
86
+ type="button"
87
+ class="flex w-full cursor-pointer items-center gap-2 rounded-md border p-2 text-left transition-colors hover:bg-accent"
88
+ >
89
+ <span
90
+ class="flex aspect-square size-8 shrink-0 items-center justify-center rounded-lg bg-primary text-primary-foreground"
91
+ >
92
+ <Icon name="lucide:git-branch" class="size-4" />
93
+ </span>
94
+
95
+ <span class="grid min-w-0 flex-1 leading-tight">
96
+ <span class="truncate font-mono text-sm font-medium">
97
+ {{ current?.label ?? duxt.version }}
98
+ </span>
99
+ <span class="truncate text-xs text-muted-foreground">
100
+ {{ (current && caption(current)) ?? $t('duxt.version.label') }}
101
+ </span>
102
+ </span>
103
+
104
+ <Icon
105
+ name="lucide:chevrons-up-down"
106
+ class="size-4 shrink-0 text-muted-foreground"
107
+ />
108
+ </button>
109
+
110
+ <button v-else type="button" class="cursor-pointer">
111
+ <UiBadge
112
+ variant="secondary"
113
+ class="gap-1 font-mono text-[10px] hover:bg-accent"
114
+ >
115
+ {{ current?.label ?? duxt.version }}
116
+ <Icon name="lucide:chevron-down" class="size-3 opacity-60" />
117
+ </UiBadge>
118
+ </button>
119
+ </UiDropdownMenuTrigger>
120
+
121
+ <!-- A project with thirty versions would otherwise open a menu the height
122
+ of the window: reka's available-height only stops it at the viewport
123
+ edge. `min()` keeps that cap and adds a shorter one, so the list
124
+ scrolls at about ten entries instead of filling the screen. -->
125
+ <UiDropdownMenuContent
126
+ align="start"
127
+ class="max-h-[min(20rem,var(--reka-dropdown-menu-content-available-height))]"
128
+ :class="
129
+ props.variant === 'block'
130
+ ? 'w-[var(--reka-dropdown-menu-trigger-width)]'
131
+ : 'w-44'
132
+ "
133
+ >
134
+ <UiDropdownMenuItem
135
+ v-for="version in versions"
136
+ :key="version.to"
137
+ as-child
138
+ >
139
+ <NuxtLink :to="pathIn(version)" class="flex items-center gap-2">
140
+ <Icon
141
+ name="lucide:check"
142
+ class="size-3.5"
143
+ :class="version.label === current?.label ? '' : 'opacity-0'"
144
+ />
145
+ <span class="font-mono text-xs">{{ version.label }}</span>
146
+ <span
147
+ v-if="caption(version)"
148
+ class="ml-auto text-xs text-muted-foreground"
149
+ >
150
+ {{ caption(version) }}
151
+ </span>
152
+ </NuxtLink>
153
+ </UiDropdownMenuItem>
154
+ </UiDropdownMenuContent>
155
+ </UiDropdownMenu>
156
+
157
+ <!-- Nothing to choose: the project's own version, stated rather than offered.
158
+ Always the badge, `block` included — a bordered box with a caption and no
159
+ control in it looks like a select that has stopped working, and the sheet
160
+ puts this one beside the brand rather than in the switcher's strip. -->
161
+ <UiBadge
162
+ v-else-if="duxt.version"
163
+ variant="secondary"
164
+ class="font-mono text-[10px]"
165
+ >
166
+ {{ duxt.version }}
167
+ </UiBadge>
168
+ </template>
@@ -0,0 +1,109 @@
1
+ <script setup lang="ts">
2
+ /**
3
+ * The visible half of the version problem.
4
+ *
5
+ * `canonical` and `noindex` tell a crawler which version to serve; they tell
6
+ * the reader nothing. Someone who arrived on v0.7.0 from a search result, or
7
+ * from a link in an old issue, sees documentation that looks exactly like the
8
+ * current one. Either half alone leaves one audience wrong, so the head tags in
9
+ * `[...slug].vue` and this banner ship together.
10
+ */
11
+ const { current, kind, preferred, preferredPath, shouldWarn } =
12
+ useDuxtVersion();
13
+ const localeLink = useDuxtLink();
14
+
15
+ const message = computed(() => `duxt.version.${kind.value}`);
16
+
17
+ /**
18
+ * An upcoming version is not a mistake to correct.
19
+ *
20
+ * Its reader is told what changes under them, not to upgrade — so the link out
21
+ * is offered only where going to the current version is actually the answer.
22
+ */
23
+ const offersUpgrade = computed(() =>
24
+ ['old', 'deprecated', 'eol'].includes(kind.value)
25
+ );
26
+
27
+ /**
28
+ * Drawn as a Callout, because the theme already has a notice and this is one.
29
+ *
30
+ * The first version of this invented a second: a full coloured border over a
31
+ * tinted surface, beside a Callout three paragraphs below it in the same quiet
32
+ * style. Two notice styles on one page is one too many — a reader learns what a
33
+ * bordered panel means once. So the colour sits on the icon and a thin left
34
+ * rule, the surface stays close to the page, and the state reads as a title
35
+ * with the sentence beneath it.
36
+ *
37
+ * Same palette as `Callout`'s variants, on purpose: sky for information, amber
38
+ * for old, red for over. Whole class names rather than composed ones, because
39
+ * Tailwind scans source text and a class assembled at runtime is not in the
40
+ * stylesheet.
41
+ */
42
+ const TONES = {
43
+ upcoming: {
44
+ rule: 'border-l-sky-500',
45
+ text: 'text-sky-500',
46
+ icon: 'lucide:flask-conical'
47
+ },
48
+ old: {
49
+ rule: 'border-l-amber-500',
50
+ text: 'text-amber-500',
51
+ icon: 'lucide:triangle-alert'
52
+ },
53
+ deprecated: {
54
+ rule: 'border-l-red-500',
55
+ text: 'text-red-500',
56
+ icon: 'lucide:triangle-alert'
57
+ },
58
+ eol: {
59
+ rule: 'border-l-red-500',
60
+ text: 'text-red-500',
61
+ icon: 'lucide:circle-off'
62
+ }
63
+ } as const;
64
+
65
+ const tone = computed(
66
+ () => TONES[kind.value as keyof typeof TONES] ?? TONES.old
67
+ );
68
+ </script>
69
+
70
+ <template>
71
+ <!-- The icon belongs to the FIRST LINE of the text, not to a line of its own.
72
+ As siblings of one flex row the sentence became its own item and wrapped
73
+ below, leaving the icon stranded above it. -->
74
+ <UiAlert
75
+ v-if="shouldWarn"
76
+ role="status"
77
+ class="mb-6 flex items-start gap-2.5 rounded-md border border-l-2 bg-muted/30 px-3 py-2.5"
78
+ :class="tone.rule"
79
+ >
80
+ <Icon
81
+ :name="tone.icon"
82
+ class="mt-[3px] size-4 shrink-0"
83
+ :class="tone.text"
84
+ />
85
+
86
+ <div class="min-w-0 flex-1">
87
+ <UiAlertTitle class="mb-0.5 font-medium text-foreground">
88
+ {{ $t(`duxt.version.titles.${kind}`) }}
89
+ </UiAlertTitle>
90
+
91
+ <UiAlertDescription class="text-muted-foreground">
92
+ {{
93
+ $t(message, {
94
+ version: current?.version ?? '',
95
+ preferred: preferred?.version ?? ''
96
+ })
97
+ }}
98
+
99
+ <NuxtLink
100
+ v-if="offersUpgrade && preferred"
101
+ :to="localeLink(preferredPath)"
102
+ class="font-medium text-foreground underline underline-offset-4"
103
+ >
104
+ {{ $t('duxt.version.goToCurrent', { preferred: preferred.version }) }}
105
+ </NuxtLink>
106
+ </UiAlertDescription>
107
+ </div>
108
+ </UiAlert>
109
+ </template>
@@ -0,0 +1,82 @@
1
+ <script setup lang="ts">
2
+ /**
3
+ * The layer's Open Graph image.
4
+ *
5
+ * Rendered by satori, not a browser, so this is a deliberately small subset of
6
+ * CSS: flexbox, colours, borders, and nothing that needs layout beyond that. No
7
+ * icon font and no external image, because satori resolves neither.
8
+ *
9
+ * The `.satori` in the file name is not decoration: nuxt-og-image reads the
10
+ * renderer out of it and ignores a template that does not name one.
11
+ *
12
+ * A consumer overrides it the ordinary way — its own
13
+ * `app/components/OgImage/Duxt.satori.vue` wins over the layer's, and pages go
14
+ * on asking for `'Duxt'`.
15
+ */
16
+ withDefaults(
17
+ defineProps<{
18
+ title?: string;
19
+ description?: string;
20
+ site?: string;
21
+ version?: string;
22
+ }>(),
23
+ {
24
+ title: 'Documentation',
25
+ description: '',
26
+ site: '',
27
+ version: ''
28
+ }
29
+ );
30
+ </script>
31
+
32
+ <template>
33
+ <div
34
+ style="
35
+ display: flex;
36
+ flex-direction: column;
37
+ justify-content: space-between;
38
+ width: 100%;
39
+ height: 100%;
40
+ padding: 80px;
41
+ background: #0a0a0a;
42
+ color: #fafafa;
43
+ font-family: sans-serif;
44
+ "
45
+ >
46
+ <div style="display: flex; align-items: center; font-size: 30px">
47
+ <span style="font-weight: 700">{{ site }}</span>
48
+ <span
49
+ v-if="version"
50
+ style="
51
+ margin-left: 20px;
52
+ padding: 6px 16px;
53
+ border: 2px solid #404040;
54
+ border-radius: 999px;
55
+ font-size: 22px;
56
+ color: #a3a3a3;
57
+ "
58
+ >
59
+ {{ version }}
60
+ </span>
61
+ </div>
62
+
63
+ <div style="display: flex; flex-direction: column">
64
+ <div style="font-size: 68px; font-weight: 700; line-height: 1.1">
65
+ {{ title }}
66
+ </div>
67
+ <div
68
+ v-if="description"
69
+ style="
70
+ margin-top: 24px;
71
+ font-size: 30px;
72
+ line-height: 1.4;
73
+ color: #a3a3a3;
74
+ "
75
+ >
76
+ {{ description.slice(0, 140) }}
77
+ </div>
78
+ </div>
79
+
80
+ <div style="display: flex; height: 10px; background: #fafafa" />
81
+ </div>
82
+ </template>
@@ -0,0 +1,37 @@
1
+ <script setup lang="ts">
2
+ import { AccordionRoot } from 'reka-ui';
3
+
4
+ /**
5
+ * `::accordion` around `:::accordion-item{label="…"}` blocks.
6
+ *
7
+ * On reka-ui's primitives, like the tree and the tabs, for the same reason:
8
+ * a hand-rolled disclosure has no keyboard handling, no `aria-expanded` and no
9
+ * relationship between the trigger and the region it opens.
10
+ *
11
+ * `type="multiple"` on purpose. A FAQ where opening one answer closes the one
12
+ * you were half-way through is a worse FAQ.
13
+ */
14
+ export interface DuxtAccordionContext {
15
+ register: (label: string) => string;
16
+ }
17
+
18
+ const items = ref<{ value: string; label: string }[]>([]);
19
+
20
+ provide<DuxtAccordionContext>('duxt-accordion', {
21
+ register(label: string) {
22
+ const value = `item-${items.value.length}`;
23
+ items.value.push({ value, label });
24
+ return value;
25
+ }
26
+ });
27
+
28
+ // The item component imports its own primitives from reka-ui. Re-exporting them
29
+ // here would be an ES module export inside <script setup>, which the SFC
30
+ // compiler rejects outright — a type export is fine, a value export is not.
31
+ </script>
32
+
33
+ <template>
34
+ <AccordionRoot type="multiple" class="my-6 rounded-lg border">
35
+ <slot />
36
+ </AccordionRoot>
37
+ </template>
@@ -0,0 +1,46 @@
1
+ <script setup lang="ts">
2
+ import {
3
+ AccordionContent,
4
+ AccordionHeader,
5
+ AccordionItem as Item,
6
+ AccordionTrigger
7
+ } from 'reka-ui';
8
+ import type { DuxtAccordionContext } from './Accordion.vue';
9
+
10
+ const props = defineProps<{ label?: string; icon?: string }>();
11
+
12
+ const parent = inject<DuxtAccordionContext | undefined>(
13
+ 'duxt-accordion',
14
+ undefined
15
+ );
16
+
17
+ const value = parent?.register(props.label ?? '') ?? 'item-0';
18
+ </script>
19
+
20
+ <template>
21
+ <Item :value="value" class="border-b last:border-b-0">
22
+ <AccordionHeader>
23
+ <AccordionTrigger
24
+ class="group flex w-full cursor-pointer items-center gap-2 px-4 py-3 text-left text-sm font-medium hover:bg-accent/50"
25
+ >
26
+ <Icon v-if="icon" :name="icon" class="size-4 text-muted-foreground" />
27
+ <span class="min-w-0">{{ label }}</span>
28
+ <Icon
29
+ name="lucide:chevron-down"
30
+ class="ml-auto size-4 shrink-0 text-muted-foreground transition-transform group-data-[state=open]:rotate-180"
31
+ />
32
+ </AccordionTrigger>
33
+ </AccordionHeader>
34
+
35
+ <!-- No enter/leave animation: the keyframes shadcn's own Accordion ships
36
+ come with that component, which this theme does not install. An
37
+ unstyled `animate-accordion-down` class is a silent no-op, and a
38
+ disclosure that opens instantly is not worse than one that appears
39
+ to. -->
40
+ <AccordionContent class="overflow-hidden">
41
+ <div class="px-4 pb-3 text-sm [&>*:last-child]:mb-0">
42
+ <slot />
43
+ </div>
44
+ </AccordionContent>
45
+ </Item>
46
+ </template>
@@ -0,0 +1,58 @@
1
+ <script setup lang="ts">
2
+ // MDC: `::callout{type="warning"}`. Quieter than a coloured panel — the colour
3
+ // sits on the icon and a thin left rule, the surface stays close to the page,
4
+ // and the title reads as text rather than as a warning label.
5
+ const props = withDefaults(
6
+ defineProps<{
7
+ type?: 'info' | 'tip' | 'warning' | 'danger';
8
+ title?: string;
9
+ icon?: string;
10
+ }>(),
11
+ { type: 'info' }
12
+ );
13
+
14
+ const variants = {
15
+ info: { icon: 'lucide:info', rule: 'border-l-sky-500', text: 'text-sky-500' },
16
+ tip: {
17
+ icon: 'lucide:lightbulb',
18
+ rule: 'border-l-emerald-500',
19
+ text: 'text-emerald-500'
20
+ },
21
+ warning: {
22
+ icon: 'lucide:triangle-alert',
23
+ rule: 'border-l-amber-500',
24
+ text: 'text-amber-500'
25
+ },
26
+ danger: {
27
+ icon: 'lucide:octagon-alert',
28
+ rule: 'border-l-red-500',
29
+ text: 'text-red-500'
30
+ }
31
+ };
32
+
33
+ const variant = computed(() => variants[props.type]);
34
+ </script>
35
+
36
+ <template>
37
+ <UiAlert
38
+ class="my-5 flex items-start gap-2.5 rounded-md border border-l-2 bg-muted/30 px-3 py-2.5"
39
+ :class="variant.rule"
40
+ >
41
+ <Icon
42
+ :name="icon ?? variant.icon"
43
+ class="mt-[3px] size-4 shrink-0"
44
+ :class="variant.text"
45
+ />
46
+
47
+ <div class="min-w-0 flex-1">
48
+ <UiAlertTitle v-if="title" class="mb-0.5 font-medium text-foreground">
49
+ {{ title }}
50
+ </UiAlertTitle>
51
+ <UiAlertDescription
52
+ class="text-muted-foreground [&>*:first-child]:mt-0 [&>*:last-child]:mb-0 [&_a]:text-foreground [&_code]:text-foreground [&_p]:my-1.5"
53
+ >
54
+ <slot />
55
+ </UiAlertDescription>
56
+ </div>
57
+ </UiAlert>
58
+ </template>
@@ -0,0 +1,91 @@
1
+ <script setup lang="ts">
2
+ /**
3
+ * One group of a release — "Features", "Bug Fixes", whatever the file said.
4
+ *
5
+ * MDC: `::changelog-group{name}`, written by `sections-changelog.ts` around the
6
+ * entries it found under one heading. The NAME IS THE FILE'S OWN, verbatim and
7
+ * untranslated: release-please, changesets and Keep a Changelog each write
8
+ * their own set, in the language the project is kept in, and a layer that
9
+ * mapped them onto a fixed taxonomy would drop the first heading it did not
10
+ * recognise. The count beside it is the entries this group lists.
11
+ *
12
+ * The entries themselves arrive in the SLOT, as the Markdown they were written
13
+ * as — so the search indexes them, `llms-full.txt` carries them and the copy
14
+ * button hands a model prose rather than a component call.
15
+ *
16
+ * A LABEL, NOT A HEADLINE. The name is set small, upper-case and in the
17
+ * group's own tone, above a list that closes up under it — the shape a release
18
+ * note actually has. Set as a 20-pixel headline it claimed the weight of a
19
+ * chapter for a word that names four bullet points, and six of them down a page
20
+ * read as six sections rather than as one release. It is still an `<h2>`: the
21
+ * outline, the contents column and the anchor are unchanged, and only its
22
+ * setting says what it is worth.
23
+ *
24
+ * The COLOUR is the one thing added to the name, and it is the same colour the
25
+ * group has on the overview — see `changelogTone`. Here it is the tone's TEXT
26
+ * pair rather than the dot's fill: a run of words and a six-pixel dot do not
27
+ * clear contrast at the same lightness. It stays a hint and never a meaning: a
28
+ * name the hint list does not know still gets a stable colour of its own, which
29
+ * is what makes the same kind of change scannable down a page of forty releases
30
+ * in any language.
31
+ *
32
+ * The `⚠` release-please prefixes its breaking block with is DRAWN AWAY rather
33
+ * than parsed away — see `changelogLabel`. The name it matches, anchors and
34
+ * filters on is still the file's own, whole.
35
+ *
36
+ * The entries' own markers are GREY, and deliberately not the tone. The colour
37
+ * is already stated once, in the label directly above them; stated again on
38
+ * every line it stopped being a signal and became a page of confetti — which is
39
+ * the failure a hint makes when it is repeated rather than placed. They are
40
+ * styled from `duxt.css`, because they arrive as Markdown from the slot.
41
+ */
42
+ const props = defineProps<{ name: string; count?: number }>();
43
+
44
+ /**
45
+ * The heading's anchor, so one group of one release can be linked to.
46
+ *
47
+ * Shared with the contents column, which is built from the page's AST and
48
+ * never sees this component render — a slug computed in both places is a
49
+ * contents column whose links go nowhere. See `app/utils/changelog.ts`.
50
+ */
51
+ const anchor = computed(() => changelogAnchor(props.name));
52
+
53
+ const tone = computed(() => changelogTone(props.name));
54
+ </script>
55
+
56
+ <template>
57
+ <section class="mt-9 first:mt-5">
58
+ <!-- Flex rather than inline runs, which is what put the count a few pixels
59
+ off the word beside it: a badge set with `align-middle` sits on the
60
+ font's own middle, and neither an upper-case label nor a 0.75rem number
61
+ is centred there. A flex line centres the boxes, and `leading-none`
62
+ is what makes the number's box its digits — with the inherited line
63
+ height it sits low by the difference. -->
64
+ <h2
65
+ :id="anchor"
66
+ class="duxt-label group mb-3 flex scroll-mt-28 items-center gap-2"
67
+ :class="tone.text"
68
+ >
69
+ <a v-if="anchor" :href="`#${anchor}`" class="duxt-heading-link">
70
+ <span class="duxt-anchor" aria-hidden="true">#</span>
71
+ {{ changelogLabel(name) }}
72
+ </a>
73
+ <template v-else>{{ changelogLabel(name) }}</template>
74
+
75
+ <!-- Outside the link: a count is a fact about the group, not part of the
76
+ words it is called. A `span`, because a heading holds phrasing
77
+ content. Its own tracking, because the label's letter-spacing is for
78
+ upper-case words and pushes a number away from nothing. -->
79
+ <span
80
+ v-if="count"
81
+ class="text-xs leading-none font-medium tracking-normal text-muted-foreground tabular-nums"
82
+ >
83
+ {{ count }}
84
+ </span>
85
+ </h2>
86
+
87
+ <div class="duxt-release">
88
+ <slot />
89
+ </div>
90
+ </section>
91
+ </template>