From 1ae05e420fd5e3e40733d1f1e1b2436ddd1e71e4 Mon Sep 17 00:00:00 2001 From: Alem Tuzlak Date: Thu, 4 Jun 2026 11:30:51 +0200 Subject: [PATCH 1/2] Add New/Updated recency pills to docs sidebar Doc pages can now surface a New or Updated pill in the docs sidebar, driven by optional `addedAt` / `updatedAt` ISO dates in each library's `docs/config.json`. Pills auto-expire after 7 days, so the sidebar self-cleans with no follow-up edits and no per-page GitHub API calls. - config.ts: add optional addedAt/updatedAt to the sidebar child valibot schema (core + per-framework) and the MenuItem type - DocsLayout.tsx: add pure getDocRecency() helper (7-day window, guards invalid and future dates) and DocRecencyPill; render in both the external and internal branches; New takes priority over Updated when both are recent - tanstack-docs-config.schema.json: add the two fields so maintainers get editor validation/autocomplete --- src/components/DocsLayout.tsx | 77 +++++++++++++++++++++++++++++++- src/utils/config.ts | 8 ++++ tanstack-docs-config.schema.json | 20 +++++++++ 3 files changed, 104 insertions(+), 1 deletion(-) diff --git a/src/components/DocsLayout.tsx b/src/components/DocsLayout.tsx index 84ef7a89c..837f7a302 100644 --- a/src/components/DocsLayout.tsx +++ b/src/components/DocsLayout.tsx @@ -24,6 +24,71 @@ import { Card } from './Card' import { PartnersRail, RightRail } from './RightRail' import { trackEvent, useTrackedImpression } from '~/utils/analytics' +// Number of days a doc page is flagged as "New"/"Updated" in the sidebar. +const RECENCY_WINDOW_DAYS = 7 +const RECENCY_WINDOW_MS = RECENCY_WINDOW_DAYS * 24 * 60 * 60 * 1000 + +type DocRecency = 'new' | 'updated' | null + +// Determine whether a doc page should show a recency pill, based on the +// maintainer-supplied `addedAt` / `updatedAt` dates in the repo's docs/config.json. +// "New" (added) takes priority over "Updated" (edited) when both are recent. +function getDocRecency(addedAt?: string, updatedAt?: string): DocRecency { + const now = Date.now() + + const isRecent = (iso?: string) => { + if (!iso) return false + const time = new Date(iso).getTime() + if (Number.isNaN(time)) return false + const age = now - time + // Reject future dates; only flag within the window. + return age >= 0 && age <= RECENCY_WINDOW_MS + } + + if (isRecent(addedAt)) return 'new' + if (isRecent(updatedAt)) return 'updated' + return null +} + +function DocRecencyPill({ + recency, + date, +}: { + recency: Exclude + date?: string +}) { + const isNew = recency === 'new' + const label = isNew ? 'New' : 'Updated' + + let title: string | undefined + if (date) { + // Parse date-only strings (YYYY-MM-DD) as local time so the tooltip doesn't + // drift to the previous day in negative-UTC timezones (new Date('2026-06-01') + // is UTC midnight, which toLocaleDateString would render as the prior day). + const dateOnly = /^(\d{4})-(\d{2})-(\d{2})$/.exec(date) + const parsed = dateOnly + ? new Date(Number(dateOnly[1]), Number(dateOnly[2]) - 1, Number(dateOnly[3])) + : new Date(date) + if (!Number.isNaN(parsed.getTime())) { + title = `${isNew ? 'Added' : 'Updated'} ${parsed.toLocaleDateString()}` + } + } + + return ( + + {label} + + ) +} + // Mobile partners strip - inline in the docs toggle bar function MobilePartnersStrip({ partners, @@ -665,6 +730,14 @@ export function DocsLayout({ ? ({ libraryId, version } as never) : undefined + const recency = getDocRecency(child.addedAt, child.updatedAt) + const recencyPill = recency ? ( + + ) : null + return (
  • {child.to.startsWith('http') ? ( @@ -674,7 +747,8 @@ export function DocsLayout({ target="_blank" rel="noopener noreferrer" > - {child.label} + {child.label} + {recencyPill} ) : ( {child.label} + {recencyPill} ) }} diff --git a/src/utils/config.ts b/src/utils/config.ts index ae3c24542..b9b5958d7 100644 --- a/src/utils/config.ts +++ b/src/utils/config.ts @@ -12,6 +12,10 @@ export type MenuItem = { label: string | React.ReactNode to: string badge?: string + /** ISO date string marking when the page was added. Drives the "New" sidebar pill. */ + addedAt?: string + /** ISO date string marking when the page was last meaningfully updated. Drives the "Updated" sidebar pill. */ + updatedAt?: string }[] collapsible?: boolean defaultCollapsed?: boolean @@ -26,6 +30,8 @@ const configSchema = v.object({ label: v.string(), to: v.string(), badge: v.optional(v.string()), + addedAt: v.optional(v.string()), + updatedAt: v.optional(v.string()), }), ), frameworks: v.optional( @@ -37,6 +43,8 @@ const configSchema = v.object({ label: v.string(), to: v.string(), badge: v.optional(v.string()), + addedAt: v.optional(v.string()), + updatedAt: v.optional(v.string()), }), ), }), diff --git a/tanstack-docs-config.schema.json b/tanstack-docs-config.schema.json index 18d02309a..9c3521d38 100644 --- a/tanstack-docs-config.schema.json +++ b/tanstack-docs-config.schema.json @@ -53,6 +53,16 @@ }, "badge": { "type": "string" + }, + "addedAt": { + "type": "string", + "format": "date", + "description": "Date the page was added (e.g. \"2026-06-01\"). Shows a \"New\" pill in the sidebar for 7 days." + }, + "updatedAt": { + "type": "string", + "format": "date", + "description": "Date the page was last meaningfully updated (e.g. \"2026-06-01\"). Shows an \"Updated\" pill in the sidebar for 7 days." } } } @@ -79,6 +89,16 @@ }, "badge": { "type": "string" + }, + "addedAt": { + "type": "string", + "format": "date", + "description": "Date the page was added (e.g. \"2026-06-01\"). Shows a \"New\" pill in the sidebar for 7 days." + }, + "updatedAt": { + "type": "string", + "format": "date", + "description": "Date the page was last meaningfully updated (e.g. \"2026-06-01\"). Shows an \"Updated\" pill in the sidebar for 7 days." } } } From 4f1f104546ded3da938b6bf63ff513005e83d37d Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Thu, 4 Jun 2026 09:32:46 +0000 Subject: [PATCH 2/2] ci: apply automated fixes --- src/components/DocsLayout.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/components/DocsLayout.tsx b/src/components/DocsLayout.tsx index 837f7a302..2aa115f38 100644 --- a/src/components/DocsLayout.tsx +++ b/src/components/DocsLayout.tsx @@ -67,7 +67,11 @@ function DocRecencyPill({ // is UTC midnight, which toLocaleDateString would render as the prior day). const dateOnly = /^(\d{4})-(\d{2})-(\d{2})$/.exec(date) const parsed = dateOnly - ? new Date(Number(dateOnly[1]), Number(dateOnly[2]) - 1, Number(dateOnly[3])) + ? new Date( + Number(dateOnly[1]), + Number(dateOnly[2]) - 1, + Number(dateOnly[3]), + ) : new Date(date) if (!Number.isNaN(parsed.getTime())) { title = `${isNew ? 'Added' : 'Updated'} ${parsed.toLocaleDateString()}`