mirror of
https://github.com/jakejarvis/stanza.git
synced 2026-07-16 18:05:58 -04:00
fix: serve docs pages in sidebar order and polish UI typography
- Add `getOrderedPages()` to `source.ts` that walks `source.getPageTree()` recursively so pages are yielded in the order declared by `meta.json` (sidebar order) rather than arbitrary filesystem order; replace `source.getPages()` in `llms-full.txt` and `docs-index.functions.ts` - Enhance `getLLMText` to prepend the page description as a blockquote when present, and `trimStart()` the processed body so the output is clean - Fix `useTimeAgo` to return `"just now"` for sub-minute diffs instead of the awkward `Intl.RelativeTimeFormat` "0 seconds ago" / "in 0 seconds" output - Tighten `LastRefreshed` markup so the tooltip wraps only the relative-time string, not the surrounding prose - Switch heading `font-semibold` → `font-medium` across stats and home pages; update category card title color from `text-muted-foreground` to `text-foreground/85` for better contrast
This commit is contained in:
@@ -13,7 +13,7 @@ function format(iso: string, now: Date): string {
|
||||
if (Number.isNaN(date.getTime())) return iso;
|
||||
const diffSec = Math.round((date.getTime() - now.getTime()) / 1000);
|
||||
const abs = Math.abs(diffSec);
|
||||
if (abs < MINUTE) return relativeFormatter.format(diffSec, "second");
|
||||
if (abs < MINUTE) return "just now";
|
||||
if (abs < HOUR) return relativeFormatter.format(Math.round(diffSec / MINUTE), "minute");
|
||||
if (abs < DAY) return relativeFormatter.format(Math.round(diffSec / HOUR), "hour");
|
||||
if (abs < MONTH) return relativeFormatter.format(Math.round(diffSec / DAY), "day");
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { docs } from "collections/server";
|
||||
import type { Node } from "fumadocs-core/page-tree";
|
||||
import { loader } from "fumadocs-core/source";
|
||||
|
||||
export const source = loader({
|
||||
@@ -27,5 +28,30 @@ export function markdownPathToSlugs(segs: string[]) {
|
||||
*/
|
||||
export async function getLLMText(page: (typeof source)["$inferPage"]) {
|
||||
const processed = await page.data.getText("processed");
|
||||
return `# ${page.data.title} (${page.url})\n\n${processed}`;
|
||||
return `# ${page.data.title} (${page.url})\n\n${page.data.description ? `> ${page.data.description.trim()}\n\n` : ""}${processed.trimStart()}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Walk `source.pageTree` and yield pages in the order declared by `meta.json`
|
||||
* (recursively into folders). `source.getPages()` returns filesystem order;
|
||||
* the tree honors the sidebar order, which is what users see.
|
||||
*/
|
||||
export function getOrderedPages() {
|
||||
const out: (typeof source)["$inferPage"][] = [];
|
||||
const visit = (nodes: Node[]) => {
|
||||
for (const node of nodes) {
|
||||
if (node.type === "page") {
|
||||
const page = source.getNodePage(node);
|
||||
if (page) out.push(page);
|
||||
} else if (node.type === "folder") {
|
||||
if (node.index) {
|
||||
const page = source.getNodePage(node.index);
|
||||
if (page) out.push(page);
|
||||
}
|
||||
visit(node.children);
|
||||
}
|
||||
}
|
||||
};
|
||||
visit(source.getPageTree().children);
|
||||
return out;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { createFileRoute } from "@tanstack/react-router";
|
||||
|
||||
import { getLLMText, source } from "@/lib/source";
|
||||
import { getLLMText, getOrderedPages } from "@/lib/source";
|
||||
|
||||
/**
|
||||
* `/llms-full.txt` — the full processed Markdown of every docs page,
|
||||
@@ -10,7 +10,7 @@ export const Route = createFileRoute("/docs/llms-full.txt")({
|
||||
server: {
|
||||
handlers: {
|
||||
GET: async () => {
|
||||
const scanned = await Promise.all(source.getPages().map(getLLMText));
|
||||
const scanned = await Promise.all(getOrderedPages().map(getLLMText));
|
||||
return new Response(scanned.join("\n\n"), {
|
||||
headers: {
|
||||
"content-type": "text/plain; charset=utf-8",
|
||||
|
||||
@@ -30,7 +30,7 @@ function Page() {
|
||||
return (
|
||||
<div className="mx-auto max-w-7xl px-4 py-10 sm:px-6">
|
||||
<header className="mb-10 max-w-2xl">
|
||||
<h1 className="text-3xl font-semibold tracking-tight">
|
||||
<h1 className="text-3xl font-medium tracking-tight">
|
||||
Build your stack, minus the pressure.
|
||||
</h1>
|
||||
<p className="mt-2 text-pretty text-muted-foreground">
|
||||
|
||||
@@ -45,6 +45,7 @@ function LastRefreshed({ iso }: { iso: string }) {
|
||||
const ago = useTimeAgo(iso);
|
||||
return (
|
||||
<p className="text-xs leading-4">
|
||||
Last refreshed{" "}
|
||||
<Tooltip>
|
||||
<TooltipTrigger
|
||||
nativeButton={false}
|
||||
@@ -55,10 +56,11 @@ function LastRefreshed({ iso }: { iso: string }) {
|
||||
/>
|
||||
}
|
||||
>
|
||||
Last refreshed {ago}.
|
||||
{ago}
|
||||
</TooltipTrigger>
|
||||
<TooltipContent sideOffset={8}>{formatGeneratedAt(iso)} UTC</TooltipContent>
|
||||
</Tooltip>
|
||||
.
|
||||
</p>
|
||||
);
|
||||
}
|
||||
@@ -95,7 +97,7 @@ function StatsPage() {
|
||||
return (
|
||||
<div className="mx-auto max-w-7xl px-4 py-10 sm:px-6">
|
||||
<header className="mb-8 max-w-2xl">
|
||||
<h1 className="mb-2 text-3xl font-semibold tracking-tight">Stats</h1>
|
||||
<h1 className="mb-2 text-3xl font-medium tracking-tight">Stats</h1>
|
||||
<p className="mb-4 text-pretty text-muted-foreground">
|
||||
What modules developers actually pick, aggregated from anonymous CLI telemetry —{" "}
|
||||
<a
|
||||
@@ -161,7 +163,7 @@ function StatsPage() {
|
||||
</section>
|
||||
|
||||
<section className="mb-8">
|
||||
<h2 className="mb-4 text-lg font-semibold tracking-tight">Popular modules by category</h2>
|
||||
<h2 className="mb-4 text-lg font-medium tracking-tight">Popular modules by category</h2>
|
||||
<div className="grid gap-4 sm:grid-cols-2 xl:grid-cols-3">
|
||||
{KNOWN_CATEGORIES.map((category) => {
|
||||
const entries = stats?.perCategory[category] ?? [];
|
||||
@@ -170,11 +172,11 @@ function StatsPage() {
|
||||
<Card key={category}>
|
||||
<CardHeader>
|
||||
<div className="flex items-baseline justify-between gap-3">
|
||||
<CardTitle className="text-xs font-medium tracking-wider text-muted-foreground uppercase">
|
||||
<CardTitle className="text-xs font-medium tracking-wider text-foreground/85 uppercase">
|
||||
{categoryLabel(category)}
|
||||
</CardTitle>
|
||||
{totalInCategory > 0 ? (
|
||||
<span className="font-mono text-xs text-muted-foreground/70 tabular-nums">
|
||||
<span className="font-mono text-xs text-muted-foreground tabular-nums">
|
||||
{numberFormatter.format(totalInCategory)}
|
||||
</span>
|
||||
) : null}
|
||||
@@ -204,11 +206,11 @@ function StatsPage() {
|
||||
</section>
|
||||
|
||||
<section id="telemetry" className="scroll-mt-20">
|
||||
<h2 className="mb-4 text-lg font-semibold tracking-tight">Telemetry policy</h2>
|
||||
<h2 className="mb-4 text-lg font-medium tracking-tight">Telemetry policy</h2>
|
||||
<div className="grid gap-4 sm:grid-cols-2">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="text-xs font-medium tracking-wider text-muted-foreground uppercase">
|
||||
<CardTitle className="text-xs font-medium tracking-wider text-foreground/85 uppercase">
|
||||
What we save
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
@@ -223,7 +225,7 @@ function StatsPage() {
|
||||
</Card>
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="text-xs font-medium tracking-wider text-muted-foreground uppercase">
|
||||
<CardTitle className="text-xs font-medium tracking-wider text-foreground/85 uppercase">
|
||||
What we don’t
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { createServerFn } from "@tanstack/react-start";
|
||||
|
||||
import { source } from "@/lib/source";
|
||||
import { getOrderedPages } from "@/lib/source";
|
||||
|
||||
export type DocsPageSummary = {
|
||||
title: string;
|
||||
@@ -20,7 +20,7 @@ export type DocsIndex = {
|
||||
*/
|
||||
export const getDocsIndex = createServerFn({ method: "GET" }).handler(
|
||||
async (): Promise<DocsIndex> => ({
|
||||
pages: source.getPages().map((p) => ({
|
||||
pages: getOrderedPages().map((p) => ({
|
||||
title: p.data.title,
|
||||
description: p.data.description,
|
||||
url: p.url,
|
||||
|
||||
Reference in New Issue
Block a user