@eqtylab/docs 0.3.4 → 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 (52) hide show
  1. package/README.md +45 -12
  2. package/dist/{chunk-VJN3HXKM.js → chunk-LNQV7TVH.js} +18 -6
  3. package/dist/chunk-LNQV7TVH.js.map +1 -0
  4. package/dist/chunk-OM7745ZQ.js +77 -0
  5. package/dist/chunk-OM7745ZQ.js.map +1 -0
  6. package/dist/{chunk-IWKZQ4CW.js → chunk-VNQZJMDO.js} +4 -2
  7. package/dist/chunk-VNQZJMDO.js.map +1 -0
  8. package/dist/{chunk-K7PJRTK3.js → chunk-X4LT7ASQ.js} +31 -4
  9. package/dist/chunk-X4LT7ASQ.js.map +1 -0
  10. package/dist/config.d.ts +10 -1
  11. package/dist/config.js +1 -1
  12. package/dist/index.js +297 -28
  13. package/dist/index.js.map +1 -1
  14. package/dist/internal/extract-versions.d.ts +33 -0
  15. package/dist/internal/rehype-base-url.d.ts +9 -2
  16. package/dist/internal/rehype-base-url.js +1 -1
  17. package/dist/internal/remark-archive-document.d.ts +6 -0
  18. package/dist/loaders.d.ts +5 -5
  19. package/dist/loaders.js +18 -7
  20. package/dist/loaders.js.map +1 -1
  21. package/dist/nav.d.ts +4 -0
  22. package/dist/nav.js +21 -7
  23. package/dist/nav.js.map +1 -1
  24. package/dist/paths.d.ts +25 -3
  25. package/dist/paths.js +1 -1
  26. package/dist/runtime/chrome/GlobalSearch.tsx +7 -2
  27. package/dist/runtime/chrome/Header.astro +9 -6
  28. package/dist/runtime/chrome/NavDrawer.astro +44 -1
  29. package/dist/runtime/chrome/NavTree.astro +1 -11
  30. package/dist/runtime/chrome/Prose.astro +18 -4
  31. package/dist/runtime/chrome/VersionBanner.astro +19 -0
  32. package/dist/runtime/chrome/VersionSwitcher.tsx +105 -0
  33. package/dist/runtime/components/CodeFence.astro +1 -1
  34. package/dist/runtime/layouts/DocsPage.astro +36 -4
  35. package/dist/runtime/layouts/DocsShell.astro +14 -3
  36. package/dist/runtime/layouts/RedirectPage.astro +19 -0
  37. package/dist/runtime/lib/nav-data.ts +44 -15
  38. package/dist/runtime/lib/versions-ui.ts +12 -0
  39. package/dist/runtime/routes/docs-md.ts +50 -11
  40. package/dist/runtime/routes/docs.astro +182 -73
  41. package/dist/runtime/routes/llms-txt.ts +2 -1
  42. package/dist/runtime/routes/not-found.astro +27 -0
  43. package/dist/runtime/styles/prose.css +9 -2
  44. package/dist/schema.d.ts +1 -0
  45. package/dist/schema.js +1 -1
  46. package/dist/versions.d.ts +37 -0
  47. package/package.json +2 -2
  48. package/dist/chunk-GSSSQTEV.js +0 -51
  49. package/dist/chunk-GSSSQTEV.js.map +0 -1
  50. package/dist/chunk-IWKZQ4CW.js.map +0 -1
  51. package/dist/chunk-K7PJRTK3.js.map +0 -1
  52. package/dist/chunk-VJN3HXKM.js.map +0 -1
package/dist/nav.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/nav.ts"],"names":[],"mappings":"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","file":"nav.js","sourcesContent":["/**\n * Pure nav-tree construction. Keep it free of fs and `astro:*` imports so it\n * runs in Node and unit tests; prev/next and breadcrumbs derive from this tree.\n */\nimport { docsHref, isActive, isAncestor, type PathContext } from './paths.ts';\nimport type { DocsBadge, DocsNavEntry, NavNode, TocNode } from './types.ts';\n\nexport type SortMode = 'alpha' | 'filename' | 'manual';\n\nexport interface GroupConfig {\n label?: string;\n icon?: string;\n order: string[];\n sort?: SortMode;\n collapsed?: boolean;\n hidden: boolean;\n badge?: DocsBadge;\n links: Array<{\n label: string;\n href: string;\n external?: boolean;\n icon?: string;\n badge?: DocsBadge;\n attrs: Record<string, string | number | boolean>;\n }>;\n}\n\nexport interface BuildNavOptions {\n entries: DocsNavEntry[];\n /** Keyed by directory relative to the content root; '' is the root group. */\n groups: Map<string, GroupConfig>;\n /** `Astro.url.pathname`. */\n currentPath: string;\n paths: PathContext;\n defaultCollapsed: boolean;\n defaultSort: SortMode;\n /** Appended after folder-derived nodes. Plugin-contributed sections land here. */\n extra?: NavNode[];\n onWarn?: (message: string) => void;\n}\n\n/** \"getting-started\" -> \"Getting Started\" */\nexport function titleCase(slug: string): string {\n return slug\n .split(/[-_]/)\n .filter(Boolean)\n .map((w) => w.charAt(0).toUpperCase() + w.slice(1))\n .join(' ');\n}\n\n/** Normalise an `order` token so 'foo', 'foo.mdx' and 'foo/' all mean the same child. */\nfunction normalizeOrderToken(token: string): string {\n return token\n .trim()\n .replace(/\\/+$/, '')\n .replace(/\\.(md|mdx)$/i, '');\n}\n\nfunction byLocale(a: string, b: string): number {\n return a.localeCompare(b, undefined, { numeric: true, sensitivity: 'base' });\n}\n\ninterface Classified {\n entry: DocsNavEntry;\n dir: string;\n name: string;\n isIndex: boolean;\n}\n\n/**\n * `isIndex` must come from `filePath`: Astro strips `/index` from ids, so\n * `guides/index.mdx` and `guides.mdx` both arrive as \"guides\".\n */\nfunction classify(entry: DocsNavEntry): Classified {\n const id = entry.id;\n const segments = id.split('/').filter(Boolean);\n const fileBase = entry.filePath\n ? (entry.filePath.split('/').pop() ?? '').replace(/\\.(md|mdx)$/i, '')\n : undefined;\n const isIndex = fileBase === 'index' || id === 'index';\n\n if (isIndex) {\n const dir = id === 'index' ? '' : segments.join('/');\n return { entry, dir, name: segments.at(-1) ?? '', isIndex: true };\n }\n return {\n entry,\n dir: segments.slice(0, -1).join('/'),\n name: segments.at(-1) ?? '',\n isIndex: false,\n };\n}\n\nfunction ancestorsOf(dir: string): string[] {\n if (!dir) return [];\n const parts = dir.split('/');\n const out: string[] = [];\n for (let i = 1; i <= parts.length; i++) out.push(parts.slice(0, i).join('/'));\n return out;\n}\n\nexport function buildNavTree(options: BuildNavOptions): NavNode[] {\n const {\n entries,\n groups,\n currentPath,\n paths,\n defaultCollapsed,\n defaultSort,\n extra = [],\n onWarn,\n } = options;\n\n const visible = entries.filter((e) => !e.draft);\n const classified = visible.map(classify);\n const indexOf = new Map<string, Classified>();\n const filesIn = new Map<string, Classified[]>();\n\n for (const c of classified) {\n if (c.isIndex) {\n indexOf.set(c.dir, c);\n } else {\n const list = filesIn.get(c.dir);\n if (list) list.push(c);\n else filesIn.set(c.dir, [c]);\n }\n }\n\n const dirs = new Set<string>(['']);\n for (const key of [...filesIn.keys(), ...indexOf.keys(), ...groups.keys()]) {\n dirs.add(key);\n for (const ancestor of ancestorsOf(key)) dirs.add(ancestor);\n }\n dirs.delete('~root'); // groups-loader sentinel, never a real directory\n\n const childDirsOf = new Map<string, string[]>();\n for (const dir of dirs) {\n if (!dir) continue;\n const parent = dir.split('/').slice(0, -1).join('/');\n const list = childDirsOf.get(parent);\n if (list) list.push(dir);\n else childDirsOf.set(parent, [dir]);\n }\n\n const groupFor = (dir: string): GroupConfig | undefined =>\n groups.get(dir === '' ? '~root' : dir) ?? groups.get(dir);\n\n const building = new Set<string>();\n\n function pageNode(c: Classified): NavNode {\n const href = docsHref(c.entry.id, paths);\n return {\n kind: 'page',\n name: c.name,\n label: c.entry.label,\n href,\n icon: c.entry.icon,\n badge: c.entry.badge,\n current: isActive(currentPath, href),\n inPath: isActive(currentPath, href),\n };\n }\n\n function groupNode(dir: string): NavNode | null {\n if (building.has(dir)) return null;\n building.add(dir);\n try {\n const cfg = groupFor(dir);\n const index = indexOf.get(dir);\n const sortMode = cfg?.sort ?? defaultSort;\n\n const pages = (filesIn.get(dir) ?? []).filter((c) => !c.entry.hidden).map(pageNode);\n const subgroups = (childDirsOf.get(dir) ?? [])\n .map(groupNode)\n .filter((n): n is NavNode => n !== null);\n\n const children = orderChildren([...pages, ...subgroups], cfg, sortMode, dir, onWarn);\n\n for (const link of cfg?.links ?? []) {\n children.push({\n kind: 'link',\n name: link.label,\n label: link.label,\n href: link.href,\n icon: link.icon,\n badge: link.badge,\n external: link.external,\n attrs: link.attrs,\n });\n }\n\n if (children.length === 0 && !index) return null;\n if (cfg?.hidden) return null;\n\n const href = index ? docsHref(index.entry.id, paths) : undefined;\n const label = cfg?.label ?? index?.entry.label ?? titleCase(dir.split('/').pop() ?? '');\n\n const inPath =\n (href ? isAncestor(currentPath, href) : false) || children.some((c) => c.inPath);\n\n return {\n kind: 'group',\n name: dir.split('/').pop() ?? '',\n label,\n href,\n icon: cfg?.icon ?? index?.entry.icon,\n badge: cfg?.badge ?? index?.entry.badge,\n current: href ? isActive(currentPath, href) : false,\n inPath,\n // Containing the current page wins over `collapsed: true`.\n open: inPath || !(cfg?.collapsed ?? defaultCollapsed),\n children,\n };\n } finally {\n building.delete(dir);\n }\n }\n\n const root = groupNode('');\n const rootChildren = root?.children ?? [];\n return [...rootChildren, ...extra];\n}\n\nfunction orderChildren(\n children: NavNode[],\n cfg: GroupConfig | undefined,\n sortMode: SortMode,\n dir: string,\n onWarn?: (message: string) => void\n): NavNode[] {\n const byName = new Map<string, NavNode>();\n for (const child of children) {\n if (!byName.has(child.name)) byName.set(child.name, child);\n }\n\n const taken = new Set<NavNode>();\n const ordered: NavNode[] = [];\n\n for (const token of cfg?.order ?? []) {\n const key = normalizeOrderToken(token);\n const hit = byName.get(key);\n if (!hit) {\n onWarn?.(\n `_group.yaml in \"${dir || '<root>'}\": order lists \"${token}\", but no child is named \"${key}\". ` +\n `Available: ${[...byName.keys()].join(', ') || '(none)'}`\n );\n continue;\n }\n if (!taken.has(hit)) {\n taken.add(hit);\n ordered.push(hit);\n }\n }\n\n let rest = children.filter((c) => !taken.has(c));\n\n if (sortMode === 'manual') {\n rest = [];\n } else if (sortMode === 'filename') {\n rest.sort((a, b) => byLocale(a.name, b.name));\n } else {\n rest.sort((a, b) => byLocale(a.label, b.label) || byLocale(a.name, b.name));\n }\n\n return [...ordered, ...rest];\n}\n\n/** Depth-first list of internal, linkable nodes. Drives prev/next. */\nexport function flattenNav(tree: NavNode[]): NavNode[] {\n const out: NavNode[] = [];\n const walk = (nodes: NavNode[]) => {\n for (const node of nodes) {\n if (node.href && node.kind !== 'link') out.push(node);\n if (node.children?.length) walk(node.children);\n }\n };\n walk(tree);\n return out;\n}\n\nexport function prevNextFor(\n tree: NavNode[],\n currentPath: string\n): { prev?: NavNode; next?: NavNode } {\n const flat = flattenNav(tree);\n const i = flat.findIndex((n) => n.href && isActive(currentPath, n.href));\n if (i === -1) return {};\n return { prev: flat[i - 1], next: flat[i + 1] };\n}\n\n/** Ancestor chain from the root down to the current page. */\nexport function breadcrumbsFor(tree: NavNode[], currentPath: string): NavNode[] {\n const trail: NavNode[] = [];\n const walk = (nodes: NavNode[]): boolean => {\n for (const node of nodes) {\n const hit = node.href ? isActive(currentPath, node.href) : false;\n if (hit) {\n trail.push(node);\n return true;\n }\n if (node.children?.length) {\n trail.push(node);\n if (walk(node.children)) return true;\n trail.pop();\n }\n }\n return false;\n };\n walk(tree);\n return trail;\n}\n\n/** Nest a flat heading list into a tree, clamped to [minLevel, maxLevel]. */\nexport function buildTocTree(\n headings: Array<{ depth: number; slug: string; text: string }>,\n { minLevel = 2, maxLevel = 3 }: { minLevel?: number; maxLevel?: number } = {}\n): TocNode[] {\n const roots: TocNode[] = [];\n const stack: TocNode[] = [];\n\n for (const h of headings) {\n if (h.depth < minLevel || h.depth > maxLevel) continue;\n const node: TocNode = { depth: h.depth, slug: h.slug, text: h.text, children: [] };\n while (stack.length && (stack.at(-1) as TocNode).depth >= node.depth) stack.pop();\n const parent = stack.at(-1);\n if (parent) parent.children.push(node);\n else roots.push(node);\n stack.push(node);\n }\n return roots;\n}\n"]}
1
+ {"version":3,"sources":["../src/nav.ts"],"names":[],"mappings":"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","file":"nav.js","sourcesContent":["/**\n * Pure nav-tree construction. Keep it free of fs and `astro:*` imports so it\n * runs in Node and unit tests; prev/next and breadcrumbs derive from this tree.\n */\nimport { docsHref, isActive, isAncestor, type PathContext } from './paths.ts';\nimport type { DocsBadge, DocsNavEntry, NavNode, TocNode } from './types.ts';\n\nexport type SortMode = 'alpha' | 'filename' | 'manual';\n\nexport interface GroupConfig {\n label?: string;\n icon?: string;\n /** Label for the row linking to this folder's own index page. */\n indexLabel?: string;\n order: string[];\n sort?: SortMode;\n collapsed?: boolean;\n hidden: boolean;\n badge?: DocsBadge;\n links: Array<{\n label: string;\n href: string;\n external?: boolean;\n icon?: string;\n badge?: DocsBadge;\n attrs: Record<string, string | number | boolean>;\n }>;\n}\n\nexport interface BuildNavOptions {\n entries: DocsNavEntry[];\n /** Keyed by directory relative to the content root; '' is the root group. */\n groups: Map<string, GroupConfig>;\n /** `Astro.url.pathname`. */\n currentPath: string;\n paths: PathContext;\n defaultCollapsed: boolean;\n defaultSort: SortMode;\n /** Label for the row linking to a section's own index page. Defaults to 'Overview'. */\n defaultIndexLabel?: string;\n /** Appended after folder-derived nodes. Plugin-contributed sections land here. */\n extra?: NavNode[];\n onWarn?: (message: string) => void;\n}\n\n/** \"getting-started\" -> \"Getting Started\" */\nexport function titleCase(slug: string): string {\n return slug\n .split(/[-_]/)\n .filter(Boolean)\n .map((w) => w.charAt(0).toUpperCase() + w.slice(1))\n .join(' ');\n}\n\n/** Normalise an `order` token so 'foo', 'foo.mdx' and 'foo/' all mean the same child. */\nfunction normalizeOrderToken(token: string): string {\n return token\n .trim()\n .replace(/\\/+$/, '')\n .replace(/\\.(md|mdx)$/i, '');\n}\n\nfunction byLocale(a: string, b: string): number {\n return a.localeCompare(b, undefined, { numeric: true, sensitivity: 'base' });\n}\n\ninterface Classified {\n entry: DocsNavEntry;\n dir: string;\n name: string;\n isIndex: boolean;\n}\n\n/**\n * `isIndex` must come from `filePath`: Astro strips `/index` from ids, so\n * `guides/index.mdx` and `guides.mdx` both arrive as \"guides\".\n */\nfunction classify(entry: DocsNavEntry): Classified {\n const id = entry.id;\n const segments = id.split('/').filter(Boolean);\n const fileBase = entry.filePath\n ? (entry.filePath.split('/').pop() ?? '').replace(/\\.(md|mdx)$/i, '')\n : undefined;\n const isIndex = fileBase === 'index' || id === 'index';\n\n if (isIndex) {\n const dir = id === 'index' ? '' : segments.join('/');\n return { entry, dir, name: segments.at(-1) ?? '', isIndex: true };\n }\n return {\n entry,\n dir: segments.slice(0, -1).join('/'),\n name: segments.at(-1) ?? '',\n isIndex: false,\n };\n}\n\nfunction ancestorsOf(dir: string): string[] {\n if (!dir) return [];\n const parts = dir.split('/');\n const out: string[] = [];\n for (let i = 1; i <= parts.length; i++) out.push(parts.slice(0, i).join('/'));\n return out;\n}\n\n/** A section's own page leads it, whether that is an `index.mdx` or a page named `overview`. */\nconst INDEX_LABEL = 'Overview';\n\nfunction isOverview(node: NavNode): boolean {\n return (\n node.kind === 'page' &&\n (node.name.toLowerCase() === INDEX_LABEL.toLowerCase() ||\n node.label.trim().toLowerCase() === INDEX_LABEL.toLowerCase())\n );\n}\n\nexport function buildNavTree(options: BuildNavOptions): NavNode[] {\n const {\n entries,\n groups,\n currentPath,\n paths,\n defaultCollapsed,\n defaultSort,\n // Never let this be blank: an older consumer's stored config predates the option, and\n // a nameless row is worse than a wrong one.\n defaultIndexLabel = INDEX_LABEL,\n extra = [],\n onWarn,\n } = options;\n\n const visible = entries.filter((e) => !e.draft);\n const classified = visible.map(classify);\n const indexOf = new Map<string, Classified>();\n const filesIn = new Map<string, Classified[]>();\n\n for (const c of classified) {\n if (c.isIndex) {\n indexOf.set(c.dir, c);\n } else {\n const list = filesIn.get(c.dir);\n if (list) list.push(c);\n else filesIn.set(c.dir, [c]);\n }\n }\n\n const dirs = new Set<string>(['']);\n for (const key of [...filesIn.keys(), ...indexOf.keys(), ...groups.keys()]) {\n dirs.add(key);\n for (const ancestor of ancestorsOf(key)) dirs.add(ancestor);\n }\n dirs.delete('~root'); // groups-loader sentinel, never a real directory\n\n const childDirsOf = new Map<string, string[]>();\n for (const dir of dirs) {\n if (!dir) continue;\n const parent = dir.split('/').slice(0, -1).join('/');\n const list = childDirsOf.get(parent);\n if (list) list.push(dir);\n else childDirsOf.set(parent, [dir]);\n }\n\n const groupFor = (dir: string): GroupConfig | undefined =>\n groups.get(dir === '' ? '~root' : dir) ?? groups.get(dir);\n\n const building = new Set<string>();\n\n function pageNode(c: Classified): NavNode {\n const href = docsHref(c.entry.id, paths);\n return {\n kind: 'page',\n name: c.name,\n label: c.entry.label,\n href,\n icon: c.entry.icon,\n badge: c.entry.badge,\n current: isActive(currentPath, href),\n inPath: isActive(currentPath, href),\n };\n }\n\n function groupNode(dir: string): NavNode | null {\n if (building.has(dir)) return null;\n building.add(dir);\n try {\n const cfg = groupFor(dir);\n const index = indexOf.get(dir);\n const sortMode = cfg?.sort ?? defaultSort;\n\n const pages = (filesIn.get(dir) ?? []).filter((c) => !c.entry.hidden).map(pageNode);\n const subgroups = (childDirsOf.get(dir) ?? [])\n .map(groupNode)\n .filter((n): n is NavNode => n !== null);\n\n const children = orderChildren([...pages, ...subgroups], cfg, sortMode, dir, onWarn);\n\n for (const link of cfg?.links ?? []) {\n children.push({\n kind: 'link',\n name: link.label,\n label: link.label,\n href: link.href,\n icon: link.icon,\n badge: link.badge,\n external: link.external,\n attrs: link.attrs,\n });\n }\n\n /*\n The folder's own index page gets a row of its own, first in the group.\n\n A group header only expands - it is never a link. Making it one where an index\n happens to exist teaches readers that headers are clickable, which is wrong for\n every section that has no index, and the page is easy to miss either way.\n */\n // `dir` is empty for the content root, whose index is the site's landing page - it\n // belongs in the header, not as a sidebar row.\n if (index && dir) {\n children.unshift({\n ...pageNode(index),\n name: cfg?.indexLabel ?? defaultIndexLabel,\n label: cfg?.indexLabel ?? defaultIndexLabel,\n });\n }\n\n if (children.length === 0) return null;\n if (cfg?.hidden) return null;\n\n const indexHref = index ? docsHref(index.entry.id, paths) : undefined;\n const label = cfg?.label ?? index?.entry.label ?? titleCase(dir.split('/').pop() ?? '');\n\n const inPath =\n (indexHref ? isAncestor(currentPath, indexHref) : false) || children.some((c) => c.inPath);\n\n return {\n kind: 'group',\n name: dir.split('/').pop() ?? '',\n label,\n icon: cfg?.icon ?? index?.entry.icon,\n badge: cfg?.badge ?? index?.entry.badge,\n inPath,\n // Containing the current page wins over `collapsed: true`.\n open: inPath || !(cfg?.collapsed ?? defaultCollapsed),\n children,\n };\n } finally {\n building.delete(dir);\n }\n }\n\n const root = groupNode('');\n const rootChildren = root?.children ?? [];\n return [...rootChildren, ...extra];\n}\n\nfunction orderChildren(\n children: NavNode[],\n cfg: GroupConfig | undefined,\n sortMode: SortMode,\n dir: string,\n onWarn?: (message: string) => void\n): NavNode[] {\n const byName = new Map<string, NavNode>();\n for (const child of children) {\n if (!byName.has(child.name)) byName.set(child.name, child);\n }\n\n const taken = new Set<NavNode>();\n const ordered: NavNode[] = [];\n\n for (const token of cfg?.order ?? []) {\n const key = normalizeOrderToken(token);\n const hit = byName.get(key);\n if (!hit) {\n onWarn?.(\n `_group.yaml in \"${dir || '<root>'}\": order lists \"${token}\", but no child is named \"${key}\". ` +\n `Available: ${[...byName.keys()].join(', ') || '(none)'}`\n );\n continue;\n }\n if (!taken.has(hit)) {\n taken.add(hit);\n ordered.push(hit);\n }\n }\n\n let rest = children.filter((c) => !taken.has(c));\n\n if (sortMode === 'manual') {\n rest = [];\n } else if (sortMode === 'filename') {\n rest.sort((a, b) => byLocale(a.name, b.name));\n } else {\n rest.sort((a, b) => byLocale(a.label, b.label) || byLocale(a.name, b.name));\n }\n\n const all = [...ordered, ...rest];\n\n // A section's overview leads it, however the author got it there - an `index.mdx`, a page\n // named `overview`, or one titled \"Overview\". Alphabetical order would bury it mid-list.\n const overview = all.filter(isOverview);\n return overview.length ? [...overview, ...all.filter((c) => !isOverview(c))] : all;\n}\n\n/** Depth-first list of internal, linkable nodes. Drives prev/next. */\nexport function flattenNav(tree: NavNode[]): NavNode[] {\n const out: NavNode[] = [];\n const walk = (nodes: NavNode[]) => {\n for (const node of nodes) {\n if (node.href && node.kind !== 'link') out.push(node);\n if (node.children?.length) walk(node.children);\n }\n };\n walk(tree);\n return out;\n}\n\nexport function prevNextFor(\n tree: NavNode[],\n currentPath: string\n): { prev?: NavNode; next?: NavNode } {\n const flat = flattenNav(tree);\n const i = flat.findIndex((n) => n.href && isActive(currentPath, n.href));\n if (i === -1) return {};\n return { prev: flat[i - 1], next: flat[i + 1] };\n}\n\n/** Ancestor chain from the root down to the current page. */\nexport function breadcrumbsFor(tree: NavNode[], currentPath: string): NavNode[] {\n const trail: NavNode[] = [];\n const walk = (nodes: NavNode[]): boolean => {\n for (const node of nodes) {\n const hit = node.href ? isActive(currentPath, node.href) : false;\n if (hit) {\n trail.push(node);\n return true;\n }\n if (node.children?.length) {\n trail.push(node);\n if (walk(node.children)) return true;\n trail.pop();\n }\n }\n return false;\n };\n walk(tree);\n return trail;\n}\n\n/** Nest a flat heading list into a tree, clamped to [minLevel, maxLevel]. */\nexport function buildTocTree(\n headings: Array<{ depth: number; slug: string; text: string }>,\n { minLevel = 2, maxLevel = 3 }: { minLevel?: number; maxLevel?: number } = {}\n): TocNode[] {\n const roots: TocNode[] = [];\n const stack: TocNode[] = [];\n\n for (const h of headings) {\n if (h.depth < minLevel || h.depth > maxLevel) continue;\n const node: TocNode = { depth: h.depth, slug: h.slug, text: h.text, children: [] };\n while (stack.length && (stack.at(-1) as TocNode).depth >= node.depth) stack.pop();\n const parent = stack.at(-1);\n if (parent) parent.children.push(node);\n else roots.push(node);\n stack.push(node);\n }\n return roots;\n}\n"]}
package/dist/paths.d.ts CHANGED
@@ -13,16 +13,38 @@ export interface PathContext {
13
13
  base: string;
14
14
  /** Mounted-at prefix for docs pages, e.g. "docs". Usually empty. */
15
15
  pathPrefix?: string;
16
- /** Version segment for a pinned build, e.g. "v3.1". Empty for latest-at-root. */
16
+ /** Version segment for an older copy, e.g. "v3.9". Empty for latest. */
17
17
  versionPrefix?: string;
18
18
  }
19
19
  /** Prefix a site-absolute path with `base`. For assets: favicons, logos, og images. */
20
20
  export declare function withBase(path: string, ctx: PathContext): string;
21
- /** The public URL for a docs collection entry id. */
21
+ /** The public URL for a docs collection entry id. The version rides in the route's slug, so it follows the prefix. */
22
22
  export declare function docsHref(id: string, ctx: PathContext): string;
23
- /** Remove `base` (and version/path prefixes) from a pathname, yielding a docs-relative path. */
23
+ /** Remove `base` (and path/version prefixes) from a pathname, yielding a docs-relative path. */
24
24
  export declare function stripBase(pathname: string, ctx: PathContext): string;
25
25
  /** Whether `target` is the page currently being viewed. Base-aware, slash-insensitive. */
26
26
  export declare function isActive(currentPathname: string, target: string): boolean;
27
27
  /** Whether `target` is the current page or an ancestor of it. Drives group expansion. */
28
28
  export declare function isAncestor(currentPathname: string, target: string): boolean;
29
+ interface VersionFallback {
30
+ base: string;
31
+ pathPrefix?: string;
32
+ /** Ids of versions that have a real copy built. */
33
+ copyIds: string[];
34
+ redirects: Array<{
35
+ id: string;
36
+ to: string | null;
37
+ }>;
38
+ }
39
+ /**
40
+ * Where to send a reader whose URL named a release with no page of its own.
41
+ *
42
+ * Static hosting emits a file per URL, and stubs are emitted for bare release URLs only, so
43
+ * `/v3.2.4/` resolves while `/v3.2.4/components/button/` reaches the not-found page. Per-page
44
+ * stubs would be roughly 3,000 files that grow with every release; this recovers the same
45
+ * destination from one page, and unlike the bare stub it keeps the page the reader asked for.
46
+ *
47
+ * Returns null when the path names no version, which is an ordinary 404.
48
+ */
49
+ export declare function resolveVersionedPath(pathname: string, opts: VersionFallback): string | null;
50
+ export {};
package/dist/paths.js CHANGED
@@ -1,3 +1,3 @@
1
- export { docsHref, ensureTrailingSlash, idToPath, isActive, isAncestor, isExternalHref, joinPath, normalizePath, stripBase, withBase } from './chunk-VJN3HXKM.js';
1
+ export { docsHref, ensureTrailingSlash, idToPath, isActive, isAncestor, isExternalHref, joinPath, normalizePath, resolveVersionedPath, stripBase, withBase } from './chunk-LNQV7TVH.js';
2
2
  //# sourceMappingURL=paths.js.map
3
3
  //# sourceMappingURL=paths.js.map
@@ -62,7 +62,10 @@ interface PagefindRawResult {
62
62
  interface PagefindApi {
63
63
  options: (opts: Record<string, unknown>) => Promise<void>;
64
64
  init: () => Promise<void>;
65
- debouncedSearch: (query: string) => Promise<{ results: PagefindRawResult[] } | null>;
65
+ debouncedSearch: (
66
+ query: string,
67
+ options?: { filters?: Record<string, string> }
68
+ ) => Promise<{ results: PagefindRawResult[] } | null>;
66
69
  }
67
70
 
68
71
  interface Props {
@@ -214,7 +217,9 @@ export default function GlobalSearch({ suggested = [] }: Props) {
214
217
  setStatus('loading');
215
218
  try {
216
219
  const api = await loadPagefind();
217
- const search = await api.debouncedSearch(value);
220
+ // The shell stamps the page's version on <html>; results stay inside it.
221
+ const version = document.documentElement.dataset.eqDocsVersion ?? 'latest';
222
+ const search = await api.debouncedSearch(value, { filters: { version } });
218
223
  // Null means a newer keystroke superseded this call.
219
224
  if (search === null || id !== requestId.current) return;
220
225
  const docs = await Promise.all(search.results.slice(0, MAX_RESULTS).map((r) => r.data()));
@@ -5,15 +5,19 @@ import { Icon } from '@eqtylab/equality';
5
5
  import GlobalSearch from './GlobalSearch.tsx';
6
6
  import LinkIcon from './LinkIcon.astro';
7
7
  import ThemeToggle from './ThemeToggle.tsx';
8
+ import VersionSwitcher from './VersionSwitcher.tsx';
9
+ import type { SwitcherData } from '../lib/versions-ui.ts';
8
10
 
9
11
  interface Props {
10
12
  /** First page of each top-level section. Shown before a reader has any history. */
11
13
  suggested?: Array<{ label: string; href: string }>;
12
14
  /** False when the page forwards a consumer-provided `search` slot. */
13
15
  showSearch?: boolean;
16
+ /** Absent when versioning is dormant or off. */
17
+ switcher?: SwitcherData;
14
18
  }
15
19
 
16
- const { showSearch = true, suggested = [] } = Astro.props;
20
+ const { showSearch = true, suggested = [], switcher } = Astro.props;
17
21
 
18
22
  const paths = { base: import.meta.env.BASE_URL, pathPrefix: CONFIG.pathPrefix };
19
23
  const homeHref = withBase(CONFIG.pathPrefix ? `/${CONFIG.pathPrefix}/` : '/', {
@@ -32,7 +36,7 @@ const navLink = 'eq-nav-control flex';
32
36
  /* Below sm the search becomes an icon in the control cluster. `order` moves it
33
37
  among these three children, so it only works while all three stay siblings. */
34
38
  }
35
- <div class="flex h-full items-center justify-between gap-4 px-4 max-sm:gap-2">
39
+ <div class="flex h-full items-center justify-between gap-6 px-4 max-sm:gap-2">
36
40
  {/* min-h-10: the mark alone leaves this link under the 24px minimum tap target. */}
37
41
  <a
38
42
  class="text-text-primary flex min-h-10 shrink-0 items-center gap-3 no-underline"
@@ -51,9 +55,7 @@ const navLink = 'eq-nav-control flex';
51
55
  alt={CONFIG.logo.alt}
52
56
  height="24"
53
57
  />
54
- ) : (
55
- <span class="whitespace-nowrap text-xl font-bold">{CONFIG.title}</span>
56
- )
58
+ ) : null
57
59
  }
58
60
  <span class="eq-display-gradient whitespace-nowrap text-xl font-bold forced-colors:text-inherit">
59
61
  {CONFIG.title}
@@ -61,7 +63,7 @@ const navLink = 'eq-nav-control flex';
61
63
  </a>
62
64
 
63
65
  {/* min-w-0: without it the search pushes the theme control off a narrow header. */}
64
- <div class="flex min-w-0 max-w-[28rem] flex-1 justify-center max-sm:order-2 max-sm:ml-auto max-sm:max-w-none max-sm:flex-none">
66
+ <div class="flex min-w-0 max-w-[22rem] flex-1 justify-center max-sm:order-2 max-sm:ml-auto max-sm:max-w-none max-sm:flex-none xl:max-w-[28rem]">
65
67
  <slot name="search" />
66
68
  {
67
69
  /* client:only: with no JS there is no search, and client:load would leave a dead control. */
@@ -71,6 +73,7 @@ const navLink = 'eq-nav-control flex';
71
73
 
72
74
  <div class="flex shrink-0 items-center gap-2 max-sm:order-3">
73
75
  <slot name="versions" />
76
+ {switcher && <VersionSwitcher client:load data={switcher} className="eq-nav-control" />}
74
77
  <div class="hidden items-center gap-1 lg:flex">
75
78
  {CONFIG.header.links.map((link) => (
76
79
  <a
@@ -10,13 +10,23 @@ import CONFIG from 'virtual:eqty-docs/config';
10
10
  import type { NavNode } from '@eqtylab/docs/types';
11
11
  import { Icon } from '@eqtylab/equality';
12
12
 
13
+ import type { SwitcherData } from '../lib/versions-ui.ts';
13
14
  import LinkIcon from './LinkIcon.astro';
14
15
  import NavTree from './NavTree.astro';
15
16
 
16
17
  interface Props {
17
18
  nodes: NavNode[];
19
+ /** Absent when versioning is dormant or off. */
20
+ switcher?: SwitcherData;
18
21
  }
19
- const { nodes } = Astro.props;
22
+ const { nodes, switcher } = Astro.props;
23
+
24
+ // data-current carries the selected state, so the rule cannot be a plain utility.
25
+ const versionLink =
26
+ 'text-text-secondary hover:text-text-primary mb-0.5 flex items-center rounded-md px-2 py-1 text-sm no-underline data-[current]:text-text-primary data-[current]:font-medium';
27
+
28
+ const versions = switcher ? [switcher.latest, ...switcher.groups.flatMap((g) => g.items)] : [];
29
+ const currentVersion = versions.find((v) => v.current) ?? switcher?.latest;
20
30
  ---
21
31
 
22
32
  {
@@ -37,6 +47,39 @@ const { nodes } = Astro.props;
37
47
  data-pagefind-ignore
38
48
  >
39
49
  <nav class="block" aria-label="Documentation">
50
+ {
51
+ /* Below lg the header bar has no room for the switcher, so the versions live here. */
52
+ }
53
+ {
54
+ /* Closed by default. Twenty-six of these at `minor`, and more at `patch`: left open they
55
+ push the navigation the reader actually came for off the screen. `<details>` because
56
+ the sidebar's groups already are, so this is the same gesture twice rather than a
57
+ third one, and it needs no script inside a popover that deliberately has none. */
58
+ }
59
+ {
60
+ switcher && currentVersion && (
61
+ <details class="border-border-raised mb-2 block border-b pb-2" data-eq-versions>
62
+ <summary class="text-text-secondary hover:text-text-primary focus-ring flex cursor-pointer select-none items-center justify-between rounded-md px-2 py-1.5 text-sm no-underline [&::-webkit-details-marker]:hidden">
63
+ <span class="flex items-center gap-2">
64
+ <span class="text-xs font-medium uppercase">Version</span>
65
+ <span class="text-text-primary font-medium">{currentVersion.label}</span>
66
+ </span>
67
+ <span class="shrink-0 transition-transform duration-200 [details[open]>summary_&]:rotate-90">
68
+ <Icon icon="ChevronRight" size="xs" />
69
+ </span>
70
+ </summary>
71
+ {/* Capped rather than unbounded: the drawer scrolls too, and two nested scrolls
72
+ with no limit means the list can still swallow the whole panel. */}
73
+ <div class="mt-1 max-h-[40vh] overflow-y-auto">
74
+ {versions.map((item) => (
75
+ <a class={versionLink} href={item.href} data-current={item.current ? '' : undefined}>
76
+ {item.label}
77
+ </a>
78
+ ))}
79
+ </div>
80
+ </details>
81
+ )
82
+ }
40
83
  {
41
84
  CONFIG.header.links.map((link) => (
42
85
  <a
@@ -47,17 +47,7 @@ const current =
47
47
  >
48
48
  <span class="flex min-w-0 flex-1 items-center gap-2 truncate">
49
49
  {node.icon && <Icon icon={node.icon} size="sm" />}
50
- {node.href ? (
51
- <a
52
- class="text-inherit no-underline"
53
- href={node.href}
54
- aria-current={node.current ? 'page' : undefined}
55
- >
56
- {node.label}
57
- </a>
58
- ) : (
59
- <span>{node.label}</span>
60
- )}
50
+ <span>{node.label}</span>
61
51
  {node.badge && (
62
52
  <Badge variant={node.badge.variant} size="sm" className="shrink-0">
63
53
  {node.badge.text}
@@ -3,6 +3,8 @@
3
3
  import { rewriteHtmlBase } from '@eqtylab/docs/internal/rehype-base-url';
4
4
  import { Icon } from '@eqtylab/equality';
5
5
 
6
+ import CONFIG from 'virtual:eqty-docs/config';
7
+
6
8
  import AlertBridge from '../components/AlertBridge.astro';
7
9
 
8
10
  interface Props {
@@ -19,17 +21,28 @@ interface Props {
19
21
  trail?: Array<{ label: string; href?: string }>;
20
22
  /** Stands in for `description` in the search index when a page has none. Not rendered. */
21
23
  fallbackSummary?: string;
24
+ /** Version id of an older copy; latest passes nothing. */
25
+ version?: string;
22
26
  }
23
27
 
24
- const { title, description, deprecated, markdownHref, source, crumbs, trail, fallbackSummary } =
25
- Astro.props;
28
+ const {
29
+ title,
30
+ description,
31
+ deprecated,
32
+ markdownHref,
33
+ source,
34
+ crumbs,
35
+ trail,
36
+ fallbackSummary,
37
+ version,
38
+ } = Astro.props;
26
39
 
27
40
  const deprecation =
28
41
  deprecated === true ? {} : deprecated && typeof deprecated === 'object' ? deprecated : null;
29
42
 
30
43
  // Authored frontmatter HTML bypasses rehype entirely, so base-prefix it here.
31
44
  const deprecationMessage = deprecation?.message
32
- ? rewriteHtmlBase(deprecation.message, import.meta.env.BASE_URL)
45
+ ? rewriteHtmlBase(deprecation.message, import.meta.env.BASE_URL, CONFIG.pathPrefix, version)
33
46
  : undefined;
34
47
 
35
48
  // Hand-rolled: every Button variant is coloured or filled, and this row is neither.
@@ -41,6 +54,7 @@ const action =
41
54
  class="eq-prose"
42
55
  data-eq-prose
43
56
  data-pagefind-body
57
+ data-pagefind-filter={`version:${version ?? 'latest'}`}
44
58
  data-pagefind-meta={crumbs ? `crumbs:${crumbs}` : undefined}
45
59
  >
46
60
  {
@@ -89,7 +103,7 @@ const action =
89
103
  title sits entirely in the gradient's flat start. */
90
104
  }
91
105
  <h1
92
- class="eq-display-gradient w-fit text-4xl font-bold forced-colors:text-text-primary"
106
+ class="w-fit text-4xl font-bold text-text-primary"
93
107
  data-pagefind-meta="title"
94
108
  >
95
109
  {title}
@@ -0,0 +1,19 @@
1
+ ---
2
+ /** Every page of an older copy opens with this. `data-pagefind-ignore`: the words "latest" and the version must not match every old page in search. */
3
+ import { Alert } from '@eqtylab/equality';
4
+
5
+ interface Props {
6
+ version: string;
7
+ latestLabel: string;
8
+ latestHref: string;
9
+ }
10
+
11
+ const { version, latestLabel, latestHref } = Astro.props;
12
+ ---
13
+
14
+ <div class="mb-8" data-eq-chrome data-pagefind-ignore role="status">
15
+ <Alert variant="warning" title={`You are reading the ${version} docs.`}>
16
+ The latest is <a class="text-text-primary underline" href={latestHref}>{latestLabel}</a>.
17
+ Interactive examples are not shown in archived pages.
18
+ </Alert>
19
+ </div>
@@ -0,0 +1,105 @@
1
+ import {
2
+ DropdownMenu,
3
+ DropdownMenuContent,
4
+ DropdownMenuEmpty,
5
+ DropdownMenuLabel,
6
+ DropdownMenuRadioGroup,
7
+ DropdownMenuRadioItem,
8
+ DropdownMenuSearch,
9
+ DropdownMenuSeparator,
10
+ DropdownMenuSub,
11
+ DropdownMenuSubContent,
12
+ DropdownMenuSubTrigger,
13
+ DropdownMenuTrigger,
14
+ Icon,
15
+ } from '@eqtylab/equality';
16
+
17
+ import type { SwitcherData } from '../lib/versions-ui.ts';
18
+
19
+ /* The trigger lays itself out. It also carries a chrome class from the header, and when that
20
+ class stopped supplying `flex` the icon, label and chevron stacked into a column and overflowed
21
+ the button. A control should not depend on its caller to sit on one line. */
22
+ const TRIGGER = 'inline-flex items-center gap-1.5 whitespace-nowrap';
23
+
24
+ /* Radix caps both surfaces at the height available below the trigger and Equality sets no
25
+ overflow, so a list taller than that cap spills instead of scrolling. Searching is when that
26
+ bites: every match is lifted out of its submenu into the root, which then holds all
27
+ twenty-seven. A `max-h` of our own is not the fix and does not even apply, measured: Equality's
28
+ own cap is unlayered CSS and beats a utility whatever the value. */
29
+ const SURFACE = 'overflow-y-auto';
30
+
31
+ interface Props {
32
+ data: SwitcherData;
33
+ /** Header's `.navLink`, so the trigger sits beside the links unaltered. */
34
+ className?: string;
35
+ }
36
+
37
+ /**
38
+ * Picks a version. `modal={false}`: a modal Radix menu closes in the same tap that opens it on
39
+ * iOS Safari, the bug the theme menu had. A radio item does not follow an href on its own,
40
+ * hence the navigation in `onValueChange`.
41
+ *
42
+ * A radio group cannot cross a portal, so each menu surface carries its own: the top level for
43
+ * latest, and one inside every major's submenu. Only the group holding the current version shows
44
+ * a checked item, which is what the reader wants.
45
+ */
46
+ export default function VersionSwitcher({ data, className }: Props) {
47
+ const items = [data.latest, ...data.groups.flatMap((g) => g.items)];
48
+ const current = items.find((i) => i.current) ?? data.latest;
49
+ const go = (href: string) => {
50
+ if (href !== current.href) window.location.assign(href);
51
+ };
52
+
53
+ return (
54
+ /* The bar has no room below lg: logo, search, theme and the drawer invoker already fill
55
+ 390px. Hidden on a wrapper, not on the trigger: the trigger also carries Header's chrome
56
+ class, and at equal specificity the bundle order decides, so `hidden` there is a coin flip. */
57
+ <div className="max-lg:hidden">
58
+ <DropdownMenu modal={false}>
59
+ <DropdownMenuTrigger asChild>
60
+ <button
61
+ type="button"
62
+ className={[className, TRIGGER].filter(Boolean).join(' ')}
63
+ aria-label={`Version: ${current.label}. Choose a version`}
64
+ >
65
+ <Icon icon="Tag" size="sm" />
66
+ <span className="text-sm">{current.label}</span>
67
+ <Icon icon="ChevronDown" size="sm" />
68
+ </button>
69
+ </DropdownMenuTrigger>
70
+ <DropdownMenuContent align="end" className={SURFACE}>
71
+ {/* Nesting holds the browse case to four rows; this holds the jump case, which nesting
72
+ makes worse by burying a version one hover deep. Typing lifts every match out of its
73
+ submenu into this list, each carrying its major as a breadcrumb. */}
74
+ <DropdownMenuSearch alwaysVisible placeholder="Search versions..." />
75
+ <DropdownMenuRadioGroup value={current.href} onValueChange={go}>
76
+ {/* A heading, not a suffix on the name: the trigger shows the version alone, and
77
+ "v4.1 (latest)" would then disagree with it and read twice in the banner. */}
78
+ <DropdownMenuLabel>Latest</DropdownMenuLabel>
79
+ <DropdownMenuRadioItem value={data.latest.href}>
80
+ {data.latest.label}
81
+ </DropdownMenuRadioItem>
82
+ </DropdownMenuRadioGroup>
83
+ <DropdownMenuSeparator />
84
+ {data.groups.map((group) => (
85
+ <DropdownMenuSub key={group.label}>
86
+ <DropdownMenuSubTrigger>
87
+ <span>{group.label}</span>
88
+ </DropdownMenuSubTrigger>
89
+ <DropdownMenuSubContent className={SURFACE}>
90
+ <DropdownMenuRadioGroup value={current.href} onValueChange={go}>
91
+ {group.items.map((item) => (
92
+ <DropdownMenuRadioItem key={item.href} value={item.href}>
93
+ {item.label}
94
+ </DropdownMenuRadioItem>
95
+ ))}
96
+ </DropdownMenuRadioGroup>
97
+ </DropdownMenuSubContent>
98
+ </DropdownMenuSub>
99
+ ))}
100
+ <DropdownMenuEmpty>No matching version</DropdownMenuEmpty>
101
+ </DropdownMenuContent>
102
+ </DropdownMenu>
103
+ </div>
104
+ );
105
+ }
@@ -26,7 +26,7 @@ const language = lang.toLowerCase();
26
26
  loses to `.code-block` and the sample silently goes back to scrolling at 16rem.
27
27
  */}
28
28
  <eq-highlight
29
- class="mb-4 block [&_[class*=code-block]]:max-h-none!"
29
+ class="mb-4 block font-mono [&_[class*=code-block]]:max-h-none!"
30
30
  data-eq-chrome
31
31
  data-eq-copy={code}
32
32
  >
@@ -7,6 +7,8 @@ import Header from '../chrome/Header.astro';
7
7
  import NavDrawer from '../chrome/NavDrawer.astro';
8
8
  import Sidebar from '../chrome/Sidebar.astro';
9
9
  import TableOfContents from '../chrome/TableOfContents.astro';
10
+ import VersionBanner from '../chrome/VersionBanner.astro';
11
+ import type { SwitcherData } from '../lib/versions-ui.ts';
10
12
 
11
13
  interface Props {
12
14
  title: string;
@@ -15,9 +17,28 @@ interface Props {
15
17
  toc: TocNode[];
16
18
  showToc?: boolean;
17
19
  splash?: boolean;
20
+ version?: string;
21
+ switcher?: SwitcherData;
22
+ noIndex?: boolean;
23
+ canonical?: string;
24
+ latestHref?: string;
25
+ latestLabel?: string;
18
26
  }
19
27
 
20
- const { title, description, nav, toc, showToc = true, splash = false } = Astro.props;
28
+ const {
29
+ title,
30
+ description,
31
+ nav,
32
+ toc,
33
+ showToc = true,
34
+ splash = false,
35
+ version,
36
+ switcher,
37
+ noIndex,
38
+ canonical,
39
+ latestHref,
40
+ latestLabel,
41
+ } = Astro.props;
21
42
 
22
43
  // A section's own index page if it has one, else its first child. Never the sidebar repeated.
23
44
  const suggested = nav
@@ -26,14 +47,20 @@ const suggested = nav
26
47
  .slice(0, 4);
27
48
  ---
28
49
 
29
- <DocsShell title={title} description={description}>
50
+ <DocsShell
51
+ title={title}
52
+ description={description}
53
+ noIndex={noIndex}
54
+ canonical={canonical}
55
+ version={version}
56
+ >
30
57
  {/* A prop, not slot fallback: Astro registers a named slot at compile time, so a forwarded
31
58
  slot always counts as filled and the fallback never runs. */}
32
- <Header showSearch={!Astro.slots.has('search')} suggested={suggested}>
59
+ <Header showSearch={!Astro.slots.has('search')} suggested={suggested} switcher={switcher}>
33
60
  <slot name="search" slot="search" />
34
61
  <slot name="versions" slot="versions" />
35
62
  </Header>
36
- <NavDrawer nodes={nav} />
63
+ <NavDrawer nodes={nav} switcher={switcher} />
37
64
  <div class="flex items-start">
38
65
  {!splash && <Sidebar nodes={nav} />}
39
66
  {/* 128px of runway, so a page stops deliberately rather than at its last line. */}
@@ -42,6 +69,11 @@ const suggested = nav
42
69
  data-splash={splash ? '' : undefined}
43
70
  >
44
71
  <div class="min-w-0 flex-1 [&>.eq-prose]:max-w-[var(--eq-docs-content-max)]">
72
+ {
73
+ version && latestHref && latestLabel && (
74
+ <VersionBanner version={version} latestLabel={latestLabel} latestHref={latestHref} />
75
+ )
76
+ }
45
77
  <slot />
46
78
  </div>
47
79
  {!splash && showToc && toc.length > 0 && <TableOfContents items={toc} />}
@@ -12,9 +12,20 @@ interface Props {
12
12
  noIndex?: boolean;
13
13
  /** Canonical URL, when it differs from the current page. */
14
14
  canonical?: string;
15
+ /** Version id of an older copy. Read by the search filter. */
16
+ version?: string;
15
17
  }
16
18
 
17
- const { title, description, noIndex, canonical } = Astro.props;
19
+ const { title, description, noIndex, canonical, version } = Astro.props;
20
+
21
+ /*
22
+ * The `font-sans` class on <body> is load-bearing. Equality declares --font-sans in
23
+ * `@theme inline`, so Tailwind emits it only where something references it, and
24
+ * Equality's preflight sets `font-family: var(--font-sans)` on html. A consumer that
25
+ * never happens to use the utility drops the variable, the preflight resolves to
26
+ * nothing, and every page silently renders in the browser's default serif. Referencing
27
+ * it here is what keeps the design system's typeface applied for every consumer.
28
+ */
18
29
 
19
30
  const paths = {
20
31
  base: import.meta.env.BASE_URL,
@@ -74,7 +85,7 @@ const themeBootstrap = `
74
85
  ---
75
86
 
76
87
  <!doctype html>
77
- <html lang="en">
88
+ <html lang="en" data-eq-docs-version={version ?? 'latest'}>
78
89
  <head>
79
90
  <meta charset="utf-8" />
80
91
  <meta name="viewport" content="width=device-width, initial-scale=1" />
@@ -86,7 +97,7 @@ const themeBootstrap = `
86
97
  <script is:inline set:html={themeBootstrap} />
87
98
  <slot name="head" />
88
99
  </head>
89
- <body>
100
+ <body class="font-sans">
90
101
  <slot />
91
102
  </body>
92
103
  </html>
@@ -0,0 +1,19 @@
1
+ ---
2
+ /** A one-line page for a release URL that has no copy of its own. Static hosting has no server redirects. */
3
+ import DocsShell from './DocsShell.astro';
4
+
5
+ interface Props {
6
+ to: string;
7
+ version: string | null;
8
+ }
9
+
10
+ const { to, version } = Astro.props;
11
+ const label = version ?? 'the latest docs';
12
+ ---
13
+
14
+ <DocsShell title="Redirecting" noIndex canonical={to} version={version ?? undefined}>
15
+ <meta slot="head" http-equiv="refresh" content={`0; url=${to}`} />
16
+ <main class="text-text-secondary mx-auto max-w-prose px-6 py-24 text-sm">
17
+ <p>This release's docs live at <a href={to}>{label}</a>.</p>
18
+ </main>
19
+ </DocsShell>