Skip to content

Comments

feat: create announcements banner#617

Open
araujogui wants to merge 1 commit intomainfrom
feat/banners
Open

feat: create announcements banner#617
araujogui wants to merge 1 commit intomainfrom
feat/banners

Conversation

@araujogui
Copy link
Member

Description

Create announcements banner component

Validation

image

Related Issues

Fixes #327

Check List

  • I have read the Contributing Guidelines and made commit messages that follow the guideline.
  • I have run node --run test and all tests passed.
  • I have check code formatting with node --run format & node --run lint.
  • I've covered new added functionality with unit tests if necessary.

@araujogui araujogui requested a review from a team as a code owner February 20, 2026 19:56
Copilot AI review requested due to automatic review settings February 20, 2026 19:56
@vercel
Copy link

vercel bot commented Feb 20, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
api-docs-tooling Ready Ready Preview Feb 20, 2026 7:57pm

Request Review

@codecov
Copy link

codecov bot commented Feb 20, 2026

Codecov Report

❌ Patch coverage is 66.66667% with 3 lines in your changes missing coverage. Please review.
✅ Project coverage is 78.62%. Comparing base (32a824c) to head (ecb53da).

Files with missing lines Patch % Lines
src/generators/web/utils/data.mjs 0.00% 2 Missing ⚠️
src/generators/jsx-ast/utils/buildContent.mjs 0.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #617      +/-   ##
==========================================
- Coverage   78.62%   78.62%   -0.01%     
==========================================
  Files         128      128              
  Lines       12461    12470       +9     
  Branches      902      902              
==========================================
+ Hits         9798     9804       +6     
- Misses       2658     2661       +3     
  Partials        5        5              

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@github-actions
Copy link

orama-db Generator

File Base Head Diff
orama-db.json 8.03 MB 8.03 MB +2.00 B (+0.00%)

web Generator

File Base Head Diff
webcrypto.js 464.93 KB 428.44 KB -36.49 KB (-7.85%)
webcrypto.html 528.45 KB 500.52 KB -27.93 KB (-5.29%)
crypto.js 1.24 MB 1.23 MB -6.32 KB (-0.50%)
crypto.html 987.57 KB 982.80 KB -4.77 KB (-0.48%)
styles.css 128.14 KB 129.88 KB +1.73 KB (+1.35%)
addons.js 299.72 KB 299.75 KB +29.00 B (+0.01%)
debugger.js 31.09 KB 31.12 KB +29.00 B (+0.09%)
report.js 188.53 KB 188.56 KB +29.00 B (+0.02%)
typescript.js 25.81 KB 25.83 KB +29.00 B (+0.11%)
cli.js 325.51 KB 325.54 KB +24.00 B (+0.01%)
deprecations.js 313.52 KB 313.55 KB +24.00 B (+0.01%)
environment_variables.js 15.13 KB 15.15 KB +24.00 B (+0.15%)
errors.js 413.89 KB 413.92 KB +24.00 B (+0.01%)
esm.js 139.78 KB 139.80 KB +24.00 B (+0.02%)
globals.js 141.34 KB 141.36 KB +24.00 B (+0.02%)
module.js 351.50 KB 351.53 KB +24.00 B (+0.01%)
modules.js 158.24 KB 158.26 KB +24.00 B (+0.01%)
n-api.js 721.30 KB 721.32 KB +24.00 B (+0.00%)
packages.js 126.22 KB 126.25 KB +24.00 B (+0.02%)
permissions.js 28.46 KB 28.48 KB +24.00 B (+0.08%)
webstreams.js 320.81 KB 320.84 KB +24.00 B (+0.01%)
assert.js 467.59 KB 467.61 KB +19.00 B (+0.00%)
async_context.js 194.17 KB 194.19 KB +19.00 B (+0.01%)
async_hooks.js 201.30 KB 201.32 KB +19.00 B (+0.01%)
buffer.js 1.13 MB 1.13 MB +19.00 B (+0.00%)
child_process.js 489.11 KB 489.13 KB +19.00 B (+0.00%)
cluster.js 206.89 KB 206.90 KB +19.00 B (+0.01%)
console.js 114.73 KB 114.75 KB +19.00 B (+0.02%)
dgram.js 185.12 KB 185.14 KB +19.00 B (+0.01%)
diagnostics_channel.js 252.87 KB 252.89 KB +19.00 B (+0.01%)
dns.js 292.44 KB 292.46 KB +19.00 B (+0.01%)
domain.js 93.08 KB 93.10 KB +19.00 B (+0.02%)
embedding.js 37.28 KB 37.30 KB +19.00 B (+0.05%)
events.js 575.74 KB 575.76 KB +19.00 B (+0.00%)
fs.js 1.33 MB 1.33 MB +19.00 B (+0.00%)
http.js 742.92 KB 742.94 KB +19.00 B (+0.00%)
http2.js 861.83 KB 861.85 KB +19.00 B (+0.00%)
https.js 166.44 KB 166.46 KB +19.00 B (+0.01%)
inspector.js 132.83 KB 132.85 KB +19.00 B (+0.01%)
intl.js 37.06 KB 37.08 KB +19.00 B (+0.05%)
net.js 321.67 KB 321.69 KB +19.00 B (+0.01%)
os.js 116.34 KB 116.36 KB +19.00 B (+0.02%)
path.js 105.32 KB 105.33 KB +19.00 B (+0.02%)
perf_hooks.js 406.03 KB 406.04 KB +19.00 B (+0.00%)
process.js 747.57 KB 747.58 KB +19.00 B (+0.00%)
punycode.js 30.23 KB 30.25 KB +19.00 B (+0.06%)
querystring.js 33.02 KB 33.04 KB +19.00 B (+0.06%)
quic.js 220.25 KB 220.27 KB +19.00 B (+0.01%)
readline.js 238.88 KB 238.89 KB +19.00 B (+0.01%)
repl.js 215.40 KB 215.42 KB +19.00 B (+0.01%)
single-executable-applications.js 83.41 KB 83.43 KB +19.00 B (+0.02%)
sqlite.js 241.73 KB 241.75 KB +19.00 B (+0.01%)
stream.js 909.92 KB 909.94 KB +19.00 B (+0.00%)
string_decoder.js 32.32 KB 32.34 KB +19.00 B (+0.06%)
synopsis.js 15.70 KB 15.72 KB +19.00 B (+0.12%)
test.js 885.03 KB 885.05 KB +19.00 B (+0.00%)
timers.js 107.82 KB 107.84 KB +19.00 B (+0.02%)
tls.js 349.38 KB 349.40 KB +19.00 B (+0.01%)
tracing.js 78.84 KB 78.86 KB +19.00 B (+0.02%)
tty.js 54.21 KB 54.23 KB +19.00 B (+0.03%)
url.js 354.43 KB 354.45 KB +19.00 B (+0.01%)
util.js 797.10 KB 797.12 KB +19.00 B (+0.00%)
v8.js 355.28 KB 355.30 KB +19.00 B (+0.01%)
vm.js 380.39 KB 380.41 KB +19.00 B (+0.00%)
wasi.js 44.80 KB 44.82 KB +19.00 B (+0.04%)
worker_threads.js 420.88 KB 420.90 KB +19.00 B (+0.00%)
zlib.js 334.44 KB 334.46 KB +19.00 B (+0.01%)
documentation.js 9.72 KB 9.74 KB +14.00 B (+0.14%)
index.js 9.51 KB 9.52 KB +14.00 B (+0.14%)

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR implements an announcement banner component that fetches configuration from a remote JSON endpoint to display global and version-specific banners on the API documentation pages. The banners support date-based activation/deactivation and can include links to relevant resources.

Changes:

  • Added AnnouncementBanner component that asynchronously fetches and displays banners from a remote config
  • Implemented isBannerActive utility function to filter banners based on date ranges
  • Added versionMajor and remoteConfig fields to static data passed from server to client

Reviewed changes

Copilot reviewed 7 out of 9 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
src/generators/web/index.mjs Adds remote config URL to web generator default configuration
src/generators/web/types.d.ts Adds remoteConfig type definition for the web generator
src/generators/web/utils/data.mjs Extracts version major and remote config URL from server config to client static data
src/generators/web/constants.mjs Registers AnnouncementBanner component in JSX imports map
src/generators/jsx-ast/utils/buildContent.mjs Adds AnnouncementBanner to document layout above navigation bar
src/generators/web/ui/utils/banner.mjs Implements date range checking logic for banner activation
src/generators/web/ui/utils/__tests__/banner.test.mjs Comprehensive tests for banner date range logic
src/generators/web/ui/components/AnnouncementBanner/types.d.ts Type definitions for banner entries and remote config structure
src/generators/web/ui/components/AnnouncementBanner/index.jsx Main component that fetches config and renders active banners

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

'#config/Logo': '@node-core/ui-components/Common/NodejsLogo',
},
remoteConfig:
'https://gist.githubusercontent.com/araujogui/8ea72ffaf574f58fca1482e764e8b5c8/raw/16af51e4efbf37da7b6aff9b7e5dd967d955aacf/api-docs.config.json',
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

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

The hardcoded Gist URL includes a specific commit hash in the raw URL. This means the configuration is pinned to a specific version and won't automatically update when the Gist is modified. If the intent is to fetch the latest version dynamically, the URL should use the format without the commit hash (e.g., ending with /raw/api-docs.config.json). If the commit hash is intentional for stability, consider documenting why this specific version is pinned and how it should be updated.

Suggested change
'https://gist.githubusercontent.com/araujogui/8ea72ffaf574f58fca1482e764e8b5c8/raw/16af51e4efbf37da7b6aff9b7e5dd967d955aacf/api-docs.config.json',
'https://gist.githubusercontent.com/araujogui/8ea72ffaf574f58fca1482e764e8b5c8/raw/api-docs.config.json',

Copilot uses AI. Check for mistakes.
Comment on lines +43 to +45
const versionBanner = config[`v${versionMajor}`]?.banner;
if (versionBanner && isBannerActive(versionBanner)) {
active.push(versionBanner);
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

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

The versionMajor is being used without a null check when constructing the version key. If config.version?.major is null (when version is not available), the template literal will create a key "vnull" which doesn't match the expected format "v24" from the issue description. Consider early returning or handling the null case explicitly before attempting to fetch version-specific banners.

Suggested change
const versionBanner = config[`v${versionMajor}`]?.banner;
if (versionBanner && isBannerActive(versionBanner)) {
active.push(versionBanner);
if (versionMajor != null) {
const versionBanner = config[`v${versionMajor}`]?.banner;
if (versionBanner && isBannerActive(versionBanner)) {
active.push(versionBanner);
}

Copilot uses AI. Check for mistakes.
Comment on lines +37 to +38
remoteConfig:
'https://gist.githubusercontent.com/araujogui/8ea72ffaf574f58fca1482e764e8b5c8/raw/16af51e4efbf37da7b6aff9b7e5dd967d955aacf/api-docs.config.json',
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

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

According to issue #327, the configuration should reside "on this repository or nodejs/node" as a static api-docs.config.json file, but this implementation uses a GitHub Gist URL instead. Consider whether this temporary Gist URL is intentional for testing/development, or if the final implementation should point to a file hosted in the official repositories. If this is a development URL, it should be documented or replaced before merging.

Copilot uses AI. Check for mistakes.
Comment on lines +60 to +67
<div>
{banners.map(banner => (
<Banner key={banner.link} type={banner.type}>
{banner.link ? <a href={banner.link}>{banner.text}</a> : banner.text}
{banner.link && <ArrowUpRightIcon />}
</Banner>
))}
</div>
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

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

The wrapper div element lacks semantic meaning. Consider using a more semantic HTML element like <aside> or adding appropriate ARIA attributes to indicate this is an announcements region. For example, <div role="region" aria-label="Announcements"> would improve accessibility by clearly identifying the purpose of this section to screen readers.

Suggested change
<div>
{banners.map(banner => (
<Banner key={banner.link} type={banner.type}>
{banner.link ? <a href={banner.link}>{banner.text}</a> : banner.text}
{banner.link && <ArrowUpRightIcon />}
</Banner>
))}
</div>
<aside aria-label="Announcements">
{banners.map(banner => (
<Banner key={banner.link} type={banner.type}>
{banner.link ? <a href={banner.link}>{banner.text}</a> : banner.text}
{banner.link && <ArrowUpRightIcon />}
</Banner>
))}
</aside>

Copilot uses AI. Check for mistakes.
<div>
{banners.map(banner => (
<Banner key={banner.link} type={banner.type}>
{banner.link ? <a href={banner.link}>{banner.text}</a> : banner.text}
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

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

External URLs from the remote config are used directly in href attributes without validation. While React/Preact provides some protection against javascript: protocol URLs, there's still a potential security risk if the remote config source is compromised. Consider validating that banner.link uses an approved protocol (https://) or implementing an allowlist of acceptable domains to ensure only trusted URLs are rendered. This is especially important since the config is fetched from an external source.

Copilot uses AI. Check for mistakes.
Comment on lines +61 to +62
{banners.map(banner => (
<Banner key={banner.link} type={banner.type}>
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

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

The key used for banners without links will be undefined, which can cause React reconciliation issues. When there are multiple banners without links, they will all have the same key (undefined), leading to incorrect updates and potential bugs in the banner display. Consider using the banner text or index as a fallback key, or use a combination of fields to ensure uniqueness. For example: key={banner.link || banner.text} or key={banner.link || ${banner.text}-${index}}.

Suggested change
{banners.map(banner => (
<Banner key={banner.link} type={banner.type}>
{banners.map((banner, index) => (
<Banner key={banner.link || `${banner.text}-${index}`} type={banner.type}>

Copilot uses AI. Check for mistakes.
Comment on lines +63 to +64
{banner.link ? <a href={banner.link}>{banner.text}</a> : banner.text}
{banner.link && <ArrowUpRightIcon />}
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

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

The ArrowUpRightIcon typically indicates an external link that opens in a new tab, but the anchor tag is missing target="_blank" and rel="noopener noreferrer" attributes. If the intention is for these links to open in a new tab (as the icon suggests), add target="_blank". For security and accessibility, also include rel="noopener noreferrer" to prevent the linked page from accessing window.opener, and consider adding an aria-label to indicate the link opens externally.

Suggested change
{banner.link ? <a href={banner.link}>{banner.text}</a> : banner.text}
{banner.link && <ArrowUpRightIcon />}
{banner.link ? (
<a
href={banner.link}
target="_blank"
rel="noopener noreferrer"
aria-label={`${banner.text} (opens in a new tab)`}
>
{banner.text}
</a>
) : (
banner.text
)}
{banner.link && <ArrowUpRightIcon aria-hidden="true" />}

Copilot uses AI. Check for mistakes.
Comment on lines +37 to +38
remoteConfig:
'https://gist.githubusercontent.com/araujogui/8ea72ffaf574f58fca1482e764e8b5c8/raw/16af51e4efbf37da7b6aff9b7e5dd967d955aacf/api-docs.config.json',
Copy link
Member

Choose a reason for hiding this comment

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

We don't have a banner config defined anywhere, so, in the meantime, can we have it read from nodejs.org's site.json (or whatever file stores the main sites' banner)

templatePath: string;
title: string;
imports: Record<string, string>;
remoteConfig: string | null;
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
remoteConfig: string | null;
remoteConfig: string;

{banners.map(banner => (
<Banner key={banner.link} type={banner.type}>
{banner.link ? <a href={banner.link}>{banner.text}</a> : banner.text}
{banner.link && <ArrowUpRightIcon />}
Copy link
Member

Choose a reason for hiding this comment

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

nit: this can go within the above line

Comment on lines +35 to +36
versionMajor: config.version?.major ?? null,
remoteConfig: config.remoteConfig ?? null,
Copy link
Member

Choose a reason for hiding this comment

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

The idea of this data is for components that can't accept the data as properties. Can we instead pass these values as properties to the component, which produces a smaller bundle?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Implement real-time (on-load) updates to API docs live-environment

2 participants