Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions app/docs/[...slug]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -84,6 +87,7 @@ export default async function Page(props: {
<DocsDescription>{page.data.description}</DocsDescription>
)}
<DocsBody className="text-fd-foreground/80">
{apiDeprecation && <ApiDeprecationNotice deprecation={apiDeprecation} />}
<MDX
components={{
...(() => {
Expand Down
25 changes: 25 additions & 0 deletions components/api/api-deprecation-notice.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<DocsCallout type="warn" title="Deprecated" art={false}>
<p>
The {name} is deprecated and will be sunset on {formatDeprecationDate(sunsetAt)}. Plan to migrate off it
before then.
</p>
<ul>
<li>
<strong>Deprecated:</strong> {formatDeprecationDate(deprecatedAt)}
</li>
<li>
<strong>Sunset:</strong> {formatDeprecationDate(sunsetAt)}
</li>
<li>
<strong>Decommissioned:</strong> {formatDeprecationDate(decommissionedAt)}
</li>
</ul>
</DocsCallout>
);
}
3 changes: 2 additions & 1 deletion components/navigation/docs-nav-config.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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: <Activity className="w-5 h-5" />,
url: '/docs/api-reference/metrics-api',
},
Expand Down
2 changes: 1 addition & 1 deletion content/docs/api-reference/metrics-api/meta.json
Original file line number Diff line number Diff line change
@@ -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": [
Expand Down
33 changes: 33 additions & 0 deletions lib/api-deprecations.ts
Original file line number Diff line number Diff line change
@@ -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`;
}
4 changes: 2 additions & 2 deletions tests/e2e/ai/visual.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<void> {
await expect
.poll(
Expand All @@ -39,7 +39,7 @@ export async function waitForStillScreen(browser: Browser): Promise<void> {
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';
}),
Expand Down
25 changes: 25 additions & 0 deletions tests/unit/docs/api-deprecations.test.ts
Original file line number Diff line number Diff line change
@@ -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');
});
});
Loading