Skip to content
Open
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
10 changes: 4 additions & 6 deletions projects/site/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
"command": "rm -rf dist .11ty-vite && pnpm exec eleventy",
"files": [
"src",
"!src/**/*.test.js",
"!src/**/*.test.lighthouse.ts",
"public",
"!public/llms.txt",
Expand Down Expand Up @@ -196,16 +197,13 @@
]
},
"test": {
"command": "vitest run --config=vitest.config.ts",
"command": "vitest run src/_11ty/layouts/metadata.test.ts",
"files": [
"src/**/*.test.ts",
"src/_11ty/layouts/metadata.js",
"src/_11ty/layouts/metadata.test.ts",
"vitest.config.ts"
],
Comment on lines +201 to +205
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Include imported module dependencies in wireit.test.files.

Line 195-198 only track metadata.js and its test file. metadata.js also imports src/_11ty/utils/env.js and src/index.11tydata.js; without those, Wireit cache invalidation can miss relevant changes.

Suggested patch
     "test": {
       "command": "vitest run src/_11ty/layouts/metadata.test.js",
       "files": [
         "src/_11ty/layouts/metadata.js",
-        "src/_11ty/layouts/metadata.test.js"
+        "src/_11ty/layouts/metadata.test.js",
+        "src/_11ty/utils/env.js",
+        "src/index.11tydata.js"
       ],
       "output": []
     },
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@projects/site/package.json` around lines 195 - 198, The wireit.test.files
array currently only lists "src/_11ty/layouts/metadata.js" and
"src/_11ty/layouts/metadata.test.js" so changes in dependencies imported by
metadata.js are not tracked; update the package.json wireit.test.files entry for
the test target to also include the imported modules "src/_11ty/utils/env.js"
and "src/index.11tydata.js" so Wireit sees changes to these symbols (metadata.js
imports env.js and index.11tydata.js) and invalidates the cache appropriately.

"output": [],
"dependencies": [
"../internals/vite:ci"
]
"output": []
},
Comment thread
coderabbitai[bot] marked this conversation as resolved.
"test:lighthouse": {
"command": "playwright-lock 'vitest run --config=vitest.lighthouse.ts'",
Expand Down
2 changes: 2 additions & 0 deletions projects/site/src/_11ty/layouts/common.js
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,8 @@ export const renderDocsNav = data => /* html */ `
<nve-tree-node ${data.page.url.includes('/docs/internal/guidelines') ? 'expanded' : ''} ${data.page.url === '/docs/internal/guidelines/' ? 'highlighted' : ''}>
<a href="docs/internal/guidelines/agent-harness/">Internal Guidelines</a>
<nve-tree-node ${data.page.url.includes('/docs/internal/guidelines/agent-harness/') ? 'highlighted selected' : ''}><a href="docs/internal/guidelines/agent-harness/">Agent Harness</a></nve-tree-node>
<nve-tree-node ${data.page.url.includes('/docs/internal/guidelines/agent-tooling/') ? 'highlighted selected' : ''}><a href="docs/internal/guidelines/agent-tooling/">Agent Tooling</a></nve-tree-node>
<nve-tree-node ${data.page.url.includes('/docs/internal/guidelines/agent-ownership/') ? 'highlighted selected' : ''}><a href="docs/internal/guidelines/agent-ownership/">Agent Ownership</a></nve-tree-node>
<nve-tree-node ${data.page.url.includes('/docs/internal/guidelines/documentation/') ? 'highlighted selected' : ''}><a href="docs/internal/guidelines/documentation/">Documentation</a></nve-tree-node>
<nve-tree-node ${data.page.url.includes('/docs/internal/guidelines/examples/') ? 'highlighted selected' : ''}><a href="docs/internal/guidelines/examples/">Examples</a></nve-tree-node>
<nve-tree-node ${data.page.url.includes('/docs/internal/guidelines/typescript/') ? 'highlighted selected' : ''}><a href="docs/internal/guidelines/typescript/">TypeScript</a></nve-tree-node>
Expand Down
227 changes: 205 additions & 22 deletions projects/site/src/_11ty/layouts/metadata.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,35 @@
import { join } from 'node:path';
import { ELEMENTS_SITE_URL } from '../utils/env.js';
import { siteData } from '../../index.11tydata.js';

export const BASE_URL = join('/', process.env.PAGES_BASE_URL ?? '', '/');

const SITE_ORIGIN = ELEMENTS_SITE_URL.replace(/\/$/, '');
const PATH_PREFIX = BASE_URL.replace(/\/$/, '');
const SITE_URL = 'https://nvidia.github.io/elements';
export const SOFTWARE_ID = `${SITE_URL}/#software`;
const SOFTWARE_URL = `${SITE_URL}/`;
const CODE_SAMPLE_ROUTES = ['/docs/cli/', '/docs/code/', '/docs/integrations/', '/docs/lint/', '/docs/mcp/'];

const LANGUAGE_NAMES = {
bash: 'Shell',
css: 'CSS',
go: 'Go',
html: 'HTML',
javascript: 'JavaScript',
js: 'JavaScript',
json: 'JSON',
markdown: 'Markdown',
md: 'Markdown',
python: 'Python',
shell: 'Shell',
sh: 'Shell',
toml: 'TOML',
ts: 'TypeScript',
tsx: 'TypeScript',
typescript: 'TypeScript',
xml: 'XML',
yaml: 'YAML',
yml: 'YAML',
zsh: 'Shell'
};

const BREADCRUMB_TERMS = {
api: 'API',
Expand Down Expand Up @@ -108,43 +132,87 @@ export function resolvePageMeta(data) {
description = `Documentation for ${rawTitle} in NVIDIA Elements, the framework-agnostic design system for AI/ML factories.`;
}

const canonicalUrl = `${SITE_ORIGIN}${PATH_PREFIX}${url}`;
const ogImage = `${SITE_ORIGIN}${PATH_PREFIX}/favicon.svg`;
const canonicalUrl = `${SITE_URL}${url}`;
const ogImage = `${SITE_URL}/favicon.svg`;
return { title, description, canonicalUrl, ogImage, url };
}

function jsonLdEncode(value) {
return JSON.stringify(value).replace(/<\//g, '<\\/');
}

export function renderJsonLd(data, meta) {
const isDocs = meta.url.startsWith('/docs/');
const articleType = isDocs ? 'TechArticle' : 'WebPage';
const date = data.page?.date instanceof Date ? data.page.date.toISOString() : new Date().toISOString();
const generatedUrls = new Set(data.collections?.all?.map(entry => entry.url).filter(Boolean));
function isApiReferencePage(data, meta) {
return Boolean(
data.tag ||
data.isApiTab ||
meta.url.startsWith('/docs/api-design/') ||
meta.url.startsWith('/docs/cli/') ||
meta.url.startsWith('/docs/integrations/') ||
meta.url.startsWith('/docs/lint/') ||
meta.url.startsWith('/docs/mcp/')
);
}

function normalizeDate(value) {
if (value instanceof Date) return value.toISOString();
if (typeof value !== 'string') return null;

const date = new Date(value);
return Number.isNaN(date.getTime()) ? null : date.toISOString();
}

function getContentDates(data) {
return {
datePublished: normalizeDate(data.datePublished ?? data.published ?? data.git?.created ?? data.git?.createdTime),
dateModified: normalizeDate(data.dateModified ?? data.modified ?? data.git?.modified ?? data.git?.modifiedTime)
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adjust so each build is not creating a new dateModified entry

};
}

function getArticle(data, meta) {
const isDocs = meta.url.startsWith('/docs/');
const isApiReference = isApiReferencePage(data, meta);
const element = findElementByTag(data.tag ?? data.component?.data?.tag);
const dates = getContentDates(data);
const article = {
'@context': 'https://schema.org',
'@type': articleType,
'@id': meta.canonicalUrl,
'@type': isApiReference ? 'APIReference' : isDocs ? 'TechArticle' : 'WebPage',
headline: meta.title,
description: meta.description,
url: meta.canonicalUrl,
mainEntityOfPage: meta.canonicalUrl,
inLanguage: 'en',
image: meta.ogImage,
datePublished: date,
dateModified: date,
publisher: { '@type': 'Organization', name: 'NVIDIA', url: SITE_ORIGIN },
publisher: { '@type': 'Organization', name: 'NVIDIA', url: SITE_URL },
author: { '@type': 'Organization', name: 'NVIDIA' }
};

if (isDocs || meta.url === '/') {
article.about = { '@id': SOFTWARE_ID };
}

if (isApiReference && element?.manifest?.tagName) {
article.programmingModel = 'Web Components';
article.targetPlatform = 'Web';
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Provide more accurate schema data for what the api doc is describing


if (element?.version) {
article.assemblyVersion = element.version;
}
}

if (dates.datePublished) article.datePublished = dates.datePublished;
if (dates.dateModified) article.dateModified = dates.dateModified;

return article;
}

function getBreadcrumb(data, meta) {
const generatedUrls = new Set(data.collections?.all?.map(entry => entry.url).filter(Boolean));
const segments = meta.url.split('/').filter(Boolean);
const articleScript = `<script type="application/ld+json">${jsonLdEncode(article)}</script>`;
if (segments.length === 0) {
return articleScript;
return null;
}

const itemListElement = [{ '@type': 'ListItem', position: 1, name: 'Home', item: `${SITE_ORIGIN}${PATH_PREFIX}/` }];
const itemListElement = [{ '@type': 'ListItem', position: 1, name: 'Home', item: `${SITE_URL}/` }];
let cumulative = '';
segments.forEach((seg, i) => {
cumulative += `/${seg}`;
Expand All @@ -155,14 +223,129 @@ export function renderJsonLd(data, meta) {
'@type': 'ListItem',
position: itemListElement.length + 1,
name: titleCaseSegment(seg),
item: `${SITE_ORIGIN}${PATH_PREFIX}${item}`
item: `${SITE_URL}${item}`
});
});

const breadcrumb = {
'@context': 'https://schema.org',
return {
'@id': `${meta.canonicalUrl}#breadcrumb`,
'@type': 'BreadcrumbList',
itemListElement
};
return `${articleScript}\n <script type="application/ld+json">${jsonLdEncode(breadcrumb)}</script>`;
}

function getSoftwareApplication(description) {
return {
'@id': SOFTWARE_ID,
'@type': 'SoftwareApplication',
name: 'NVIDIA Elements',
description,
url: SOFTWARE_URL,
applicationCategory: 'DeveloperApplication',
operatingSystem: 'Any',
runtimePlatform: 'Web',
softwareHelp: SOFTWARE_URL
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the base meta schema to describe most pages

};
}

function getPageText(data) {
return [data.content, data.rawInput, data.templateContent].filter(value => typeof value === 'string').join('\n');
}

function normalizeLanguage(language) {
if (typeof language !== 'string') return null;
return LANGUAGE_NAMES[language.toLowerCase()] ?? null;
}

function getProgrammingLanguages(data) {
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a rudimentary way to detect code fences/blocks to determine what language information can be provided in the schema/metadata

const languages = new Set();
const declared = data.programmingLanguage ?? data.programmingLanguages;
const declaredLanguages = Array.isArray(declared) ? declared : [declared].filter(Boolean);
declaredLanguages
.map(String)
.map(normalizeLanguage)
.filter(Boolean)
.forEach(language => languages.add(language));

if (data.isExamplesTab) {
languages.add('HTML');
}

const pageText = getPageText(data);
[...pageText.matchAll(/(?:language|lang)=["']([^"']+)["']/g)].forEach(match => {
const language = normalizeLanguage(match[1]);
if (language) languages.add(language);
});

[...pageText.matchAll(/class=["'][^"']*language-([a-z0-9+-]+)[^"']*["']/gi)].forEach(match => {
const language = normalizeLanguage(match[1]);
if (language) languages.add(language);
});

[...pageText.matchAll(/```([a-z0-9+-]+)/gi)].forEach(match => {
const language = normalizeLanguage(match[1]);
if (language) languages.add(language);
});

return [...languages];
}

function hasVisibleCode(data) {
return getProgrammingLanguages(data).length > 0;
}

function shouldEmitSoftwareSourceCode(data, meta) {
if (data.structuredData?.sourceCode === false) return false;
if (data.structuredData?.sourceCode === true) return true;
if (data.isExamplesTab) return true;
if (!hasVisibleCode(data)) return false;
return CODE_SAMPLE_ROUTES.some(route => meta.url.startsWith(route));
}

function getCodeSampleType(data, meta) {
if (data.structuredData?.codeSampleType) return data.structuredData.codeSampleType;
if (meta.url.startsWith('/starters/')) return 'template';
if (data.isExamplesTab || meta.url.includes('/examples/')) return 'full solution';
return 'code snippet';
}

function getRuntimePlatform(meta) {
return meta.url.startsWith('/docs/cli/') || meta.url.startsWith('/docs/mcp/') ? 'Native binary' : 'Web';
}

function getSoftwareSourceCode(data, meta) {
if (!shouldEmitSoftwareSourceCode(data, meta)) return null;

const programmingLanguage = getProgrammingLanguages(data);
if (!programmingLanguage.length) return null;

return {
'@id': `${meta.canonicalUrl}#source-code`,
'@type': 'SoftwareSourceCode',
name: meta.title,
description: meta.description,
url: meta.canonicalUrl,
codeSampleType: getCodeSampleType(data, meta),
programmingLanguage: programmingLanguage.length === 1 ? programmingLanguage[0] : programmingLanguage,
runtimePlatform: getRuntimePlatform(meta),
targetProduct: { '@id': SOFTWARE_ID, '@type': 'SoftwareApplication' }
};
}

export function renderJsonLd(data, meta) {
const article = getArticle(data, meta);
const breadcrumb = getBreadcrumb(data, meta);
const sourceCode = getSoftwareSourceCode(data, meta);
const graph = [
article,
...(breadcrumb ? [breadcrumb] : []),
...(meta.url === '/' ? [getSoftwareApplication(meta.description)] : []),
...(sourceCode ? [sourceCode] : [])
];

if (sourceCode) {
article.hasPart = { '@id': sourceCode['@id'] };
}

return `<script type="application/ld+json">${jsonLdEncode({ '@context': 'https://schema.org', '@graph': graph })}</script>`;
}
Loading