diff --git a/app/docs/[...slug]/page.tsx b/app/docs/[...slug]/page.tsx index d5b92c07022..57b701a91bb 100644 --- a/app/docs/[...slug]/page.tsx +++ b/app/docs/[...slug]/page.tsx @@ -7,8 +7,10 @@ import StateGrowthChart from "@/components/content-design/state-growth-chart"; import { BackToTop } from "@/components/ui/back-to-top"; import { Feedback } from "@/components/ui/feedback"; import { SidebarActions } from "@/components/ui/sidebar-actions"; +import { ApiDeprecationNotice } from "@/components/api/api-deprecation-notice"; import { CChainAPIPage, DataAPIPage, MetricsAPIPage, PChainAPIPage, VerificationAPIPage, XChainAPIPage } from "@/components/api/api-pages"; import AddNetworkButtonInline from "@/components/client/AddNetworkButtonInline"; +import { getApiDeprecation } from "@/lib/api-deprecations"; import { documentation } from "@/lib/source"; import { createMetadata } from "@/utils/metadata"; import { sharedMDXComponents } from "@/components/mdx/shared-components"; @@ -45,6 +47,7 @@ export default async function Page(props: { if (!page) notFound(); const { body: MDX, toc } = await page.data.load(); + const apiDeprecation = getApiDeprecation(page.slugs); // Use page.path which contains the actual file path relative to collection root // (e.g., "tooling/avalanche-sdk/index.mdx" for an index file) // This correctly handles both regular .mdx files and index.mdx files @@ -84,6 +87,7 @@ export default async function Page(props: { {page.data.description} )} + {apiDeprecation && } { diff --git a/components/api/api-deprecation-notice.tsx b/components/api/api-deprecation-notice.tsx new file mode 100644 index 00000000000..8d3b79dd4a1 --- /dev/null +++ b/components/api/api-deprecation-notice.tsx @@ -0,0 +1,25 @@ +import { DocsCallout } from '@/components/docs-book/callout'; +import { formatDeprecationDate, type ApiDeprecation } from '@/lib/api-deprecations'; + +export function ApiDeprecationNotice({ deprecation }: { deprecation: ApiDeprecation }) { + const { name, deprecatedAt, sunsetAt, decommissionedAt } = deprecation; + return ( + +

+ The {name} is deprecated and will be sunset on {formatDeprecationDate(sunsetAt)}. Plan to migrate off it + before then. +

+
    +
  • + Deprecated: {formatDeprecationDate(deprecatedAt)} +
  • +
  • + Sunset: {formatDeprecationDate(sunsetAt)} +
  • +
  • + Decommissioned: {formatDeprecationDate(decommissionedAt)} +
  • +
+
+ ); +} diff --git a/components/navigation/docs-nav-config.tsx b/components/navigation/docs-nav-config.tsx index 8620f1bc018..c37718d80f2 100644 --- a/components/navigation/docs-nav-config.tsx +++ b/components/navigation/docs-nav-config.tsx @@ -96,7 +96,8 @@ export const apiReferenceOptions: NavOption[] = [ }, { title: 'Metrics API', - description: 'Network metrics and statistics', + badge: 'Deprecated', + description: 'Deprecated: network metrics and statistics', icon: , url: '/docs/api-reference/metrics-api', }, diff --git a/content/docs/api-reference/metrics-api/meta.json b/content/docs/api-reference/metrics-api/meta.json index 093e32a409b..76d13d6f2f6 100644 --- a/content/docs/api-reference/metrics-api/meta.json +++ b/content/docs/api-reference/metrics-api/meta.json @@ -1,6 +1,6 @@ { "title": "Metrics API", - "description": "Popsicle Metrics API for Avalanche network metrics", + "description": "Deprecated: Popsicle Metrics API for Avalanche network metrics", "icon": "ChartLine", "root": true, "pages": [ diff --git a/lib/api-deprecations.ts b/lib/api-deprecations.ts new file mode 100644 index 00000000000..4fe9dedb3af --- /dev/null +++ b/lib/api-deprecations.ts @@ -0,0 +1,33 @@ +export interface ApiDeprecation { + name: string; + /** Docs slugs under /docs that this deprecation covers, e.g. ['api-reference', 'metrics-api']. */ + slugPrefix: string[]; + /** ISO 8601 timestamps in UTC. */ + deprecatedAt: string; + sunsetAt: string; + decommissionedAt: string; +} + +export const API_DEPRECATIONS: ApiDeprecation[] = [ + { + name: 'Metrics API', + slugPrefix: ['api-reference', 'metrics-api'], + deprecatedAt: '2026-11-05T00:00:00Z', + sunsetAt: '2026-11-16T12:00:00Z', + decommissionedAt: '2026-11-30T12:00:00Z', + }, +]; + +export function getApiDeprecation(slugs: string[]): ApiDeprecation | undefined { + return API_DEPRECATIONS.find(({ slugPrefix }) => slugPrefix.every((part, i) => slugs[i] === part)); +} + +const dateFormat = new Intl.DateTimeFormat('en-US', { dateStyle: 'long', timeZone: 'UTC' }); +const timeFormat = new Intl.DateTimeFormat('en-US', { hour: '2-digit', minute: '2-digit', hourCycle: 'h23', timeZone: 'UTC' }); + +/** "November 16, 2026", or "November 16, 2026 12:00 UTC" when the time is not midnight. */ +export function formatDeprecationDate(iso: string): string { + const date = new Date(iso); + const day = dateFormat.format(date); + return date.getUTCHours() === 0 && date.getUTCMinutes() === 0 ? day : `${day} ${timeFormat.format(date)} UTC`; +} diff --git a/tests/e2e/ai/visual.ts b/tests/e2e/ai/visual.ts index b08d65d77a2..59d6b8d7de5 100644 --- a/tests/e2e/ai/visual.ts +++ b/tests/e2e/ai/visual.ts @@ -24,7 +24,7 @@ export function layoutIsIntact(subject: string, normal: readonly string[] = []): } // Waits until the screen the judge sees has settled. First the page is at the top, the web fonts have loaded and -// the images in view have decoded. A timeout names the image (a broken image in view never loads). +// the image requests in view have finished. A timeout names an image whose request is still pending. export async function waitForStillScreen(browser: Browser): Promise { await expect .poll( @@ -39,7 +39,7 @@ export async function waitForStillScreen(browser: Browser): Promise { const inHeight = box.bottom > 0 && box.top < window.innerHeight; return box.width > 0 && inHeight && box.right > 0 && box.left < window.innerWidth; }) - .filter((image) => !(image.complete && image.naturalWidth > 0)) + .filter((image) => !image.complete) .map((image) => image.currentSrc || image.src); return loading.length > 0 ? `images not loaded: ${loading.slice(0, 3).join(', ')}` : 'loaded'; }), diff --git a/tests/unit/docs/api-deprecations.test.ts b/tests/unit/docs/api-deprecations.test.ts new file mode 100644 index 00000000000..f9b38998849 --- /dev/null +++ b/tests/unit/docs/api-deprecations.test.ts @@ -0,0 +1,25 @@ +import { describe, expect, it } from 'vitest'; +import { formatDeprecationDate, getApiDeprecation } from '@/lib/api-deprecations'; + +describe('getApiDeprecation', () => { + it('covers every page of the Metrics API reference', () => { + expect(getApiDeprecation(['api-reference', 'metrics-api'])?.name).toBe('Metrics API'); + expect(getApiDeprecation(['api-reference', 'metrics-api', 'evm-chains', 'getEvmChainMetrics'])?.name).toBe( + 'Metrics API', + ); + }); + + it('leaves other pages alone', () => { + expect(getApiDeprecation(['api-reference', 'data-api'])).toBeUndefined(); + expect(getApiDeprecation(['api-reference', 'webhook-api', 'webhooks', 'createWebhook'])).toBeUndefined(); + expect(getApiDeprecation(['api-reference'])).toBeUndefined(); + expect(getApiDeprecation(['rpcs', 'other', 'metrics-api'])).toBeUndefined(); + }); +}); + +describe('formatDeprecationDate', () => { + it('shows the UTC time unless it is midnight', () => { + expect(formatDeprecationDate('2026-11-05T00:00:00Z')).toBe('November 5, 2026'); + expect(formatDeprecationDate('2026-11-16T12:00:00Z')).toBe('November 16, 2026 12:00 UTC'); + }); +});