Skip to content

feat(components): add Carbon Ads top bar and sidebar - #8489

Open
avivkeller wants to merge 1 commit into
mainfrom
cursor/carbon-ads-docs
Open

avivkeller wants to merge 1 commit into
mainfrom
cursor/carbon-ads-docs

Conversation

@avivkeller

@avivkeller avivkeller commented Nov 10, 2026 •

Copy link
Copy Markdown
Member

Summary

Adds Carbon Ads to the documentation pages, ported from webpack/webpack-doc-kit#294, with the visual styling from nodejs/nodejs.org#9250.

  • Top bar (zone CWBI623E): a dismissible banner above the page content. Closing it hides it for the rest of the session without requesting another ad.
  • Sidebar card (zone CWBI6K77): shown at the bottom of the docs sidebar, and only requested at md and up, where the sidebar is visible.
  • Ads are fetched from Carbon's JSON API. A view beacon fires once at least 50% of the ad has been visible for one continuous second.
  • Each page change fetches a fresh ad. The homepage, the 404 page, and printable pages render no ad markup and make no requests.
  • Advertiser colors are applied through custom properties, including the dark-theme palette. The stylesheet is global CSS, not a CSS module, so those property names are not renamed.

Things to check before merging

  • Impression counting. Requests are sent without ignore=yes, so local and preview builds count impressions. Carbon's nodejs.org integration gates this behind a production build flag. Decide whether this site should do the same.
  • Zone IDs and approval. The zone IDs and the creatives need Carbon's sign-off before this ships.

Testing

  • Checks on the changed files: ESLint, Prettier, cspell, and the Jest Page test all pass.
  • The dev server was checked in the browser on /concepts/ in light and dark themes, at a phone width, after dismissing the banner, and after navigating to /guides/.
  • Not run: the full Jest suite, the production build, and the link checker.

Assisted-by: Cursor

Made with Cursor

Summary by CodeRabbit

  • New Features
    • Added Carbon advertisements above page content and in the sidebar on eligible pages.
    • Sidebar ads appear on wider screens. Top-bar ads can be dismissed for the remainder of the session.
    • Ads display available images, descriptions, advertiser links, and calls to action. Printable pages do not display these ads.
  • Chores
    • Updated spell-check and linting configuration.

@vercel

vercel Bot commented Nov 10, 2026 •

Copy link
Copy Markdown

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

Project Deployment Actions Updated
webpack-js-org Ready Ready Preview Nov 10, 2026 4:09pm UTC

Request Review

@linux-foundation-easycla

linux-foundation-easycla Bot commented Nov 10, 2026 •

Copy link
Copy Markdown

CLA Signed
The committers listed above are authorized under a signed CLA.

  • ✅ login: avivkeller / name: avivkeller (7b4b2a4)

Show Carbon Ads on documentation pages: a dismissible banner above the page content and a card at the bottom of the sidebar (desktop only). Ads are fetched from Carbon's JSON API, and views are only beaconed once at least half of the ad has been visible for a second.

Assisted-by: Cursor
@avivkeller
avivkeller force-pushed the cursor/carbon-ads-docs branch from 07a764f to 7b4b2a4 Compare November 10, 2026 16:06
@coderabbitai

coderabbitai Bot commented Nov 10, 2026 •

Copy link
Copy Markdown

Explain Change →

Walkthrough

The change adds a Carbon ad hook that fetches ads, maps ad colors, and tracks views. It adds top-bar and sidebar ad components with shared styles and session-based dismissal for the top-bar ad. The page and sidebar layouts render these components except on printable paths. The ESLint configuration and spell-check dictionary also include entries for the new code and ad fields.

Pre-merge checks: Passed 5

Check name Status Explanation
Description Check Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check Passed The title clearly summarizes the main change: adding Carbon Ads to the top bar and sidebar.
Docstring Coverage Passed Docstring coverage is 83.33% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff with cyclomatic complexity of at least 2. Documentati…
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.
Full details: Docstring Coverage

Explanation

Docstring coverage is 83.33% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff with cyclomatic complexity of at least 2. Documentation-only edits and deletions within surviving functions are included; unchanged diff context and fully deleted functions are excluded. Analyzed 6 functions across 6 files. (2 skipped: 2 unsupported.)

Finishing Touches

Generate docstrings

  • Commit to this branch
  • Create a new PR
Generate unit tests (beta)

  • Commit to this branch
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Warning

Tools execution failed with the following error:

Failed to run tools: 13 INTERNAL: Received RST_STREAM with code 2 (Internal server error)

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

Share

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 3

Additional comments: 1

src/components/CarbonAd/useCarbonAd.mjs: 1

37-61: Trivial · Nitpick

Unverified finding
Verification ran but could not confirm this finding. It is shown for review, not as a verified issue.

Gap: the one-per-mount fetch also fires on dev and preview builds.

The hook sends the ad request and the view beacon on every build, including local and preview builds. Those builds count impressions. The PR description lists this as an open decision. Decide before merge whether to gate the hook on a production-only flag, for example a webpack DefinePlugin constant.

Review info

Run configuration

  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 7d2d8001-3c34-4df9-9ca1-4fd10e632ec6
Commits

Reviewing files that changed from the base of the PR and between 9cfb83c and 07a764f.

Files selected for processing: 8

  • cspell.json
  • eslint.config.mjs
  • src/components/CarbonAd/SideBar.jsx
  • src/components/CarbonAd/TopBar.jsx
  • src/components/CarbonAd/carbon-ad.css
  • src/components/CarbonAd/useCarbonAd.mjs
  • src/components/Page/Page.jsx
  • src/components/Sidebar/Sidebar.jsx

Included review availability: This review used your included allowance. Your plan provides up to 8 included reviews per hour; 7 remain after this review.

Comment on lines +51 to +60
/>
</button>
</aside>
);
}

Ad.propTypes = {
onClose: PropTypes.func.isRequired,
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Minor · Functional Correctness · Quick win

Supported by static analysis

Script executed:

sed -n '1,110p' src/components/CarbonAd/TopBar.jsx
sed -n '1,115p' src/components/CarbonAd/carbon-ad.css
sed -n '110,140p' src/components/Page/Page.jsx

Repository: webpack/webpack.js.org

Length of output: 4880


Script executed:

git diff --stat 9cfb83cff4c755585ef649a81d3c1a56e12b68fe 07a764ff37ca3fb891232a40000fc79086f4486a -- src/components/CarbonAd src/components/Page
cat -n src/components/CarbonAd/useCarbonAd.mjs
cat -n src/components/CarbonAd/TopBar.jsx
cat -n src/components/CarbonAd/Sidebar.jsx 2>/dev/null
cat -n src/components/Page/Page.jsx | sed -n '1,150p'
rg -n -F -e 'TopBarAd' -e 'SidebarAd' -e 'useCarbonAd(' -e '--breakpoint-md' -- src || test "$?" -eq 1
ls src/components/CarbonAd

Repository: webpack/webpack.js.org

Length of output: 12402


Script executed:

cat -n src/components/CarbonAd/SideBar.jsx
cat -n src/components/CarbonAd/carbon-ad.css
cat -n src/styles/tailwind.css | sed -n '1,40p'
rg -n -F -e 'from "../Page/Page' -e 'from "./Page/Page' -e '<Page' -e 'CarbonAd/SideBar' -- src || test "$?" -eq 1
git diff 9cfb83cff4c755585ef649a81d3c1a56e12b68fe 07a764ff37ca3fb891232a40000fc79086f4486a -- src/components/Page/Page.jsx

Repository: webpack/webpack.js.org

Length of output: 6936


Script executed:

cat -n src/components/Site/Site.jsx | sed -n '265,390p'
cat -n src/components/Sidebar/Sidebar.jsx | sed -n '1,120p'
rg -n -F -e 'PageElement' -e 'printable' -- src/components/Site/Site.jsx

Repository: webpack/webpack.js.org

Length of output: 8877


Restrict the top-bar ad to md and wider

On a phone-width documentation page, useCarbonAd("CWBI623E") can request and display the banner because it has no viewport guard. Gate the request below 768px and hide the banner when the viewport narrows.

Comment on lines +63 to +65
const [dismissed, setDismissed] = useState(
() => globalThis.sessionStorage?.getItem(DISMISSED_KEY) === "true",
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Minor · Stability & Availability · Quick win

Guard the sessionStorage read.

Accessing globalThis.sessionStorage can throw a SecurityError when the browser blocks storage. The throw happens during render and can break the whole Page. Wrap the read in try/catch and default to false.

Proposed fix

--- "a/src/components/CarbonAd/TopBar.jsx"
+++ "b/src/components/CarbonAd/TopBar.jsx"
@@ -60,9 +60,13 @@
 
 export default function TopBarAd() {
   // There is no storage while rendering on the server.
-  const [dismissed, setDismissed] = useState(
-    () => globalThis.sessionStorage?.getItem(DISMISSED_KEY) === "true",
-  );
+  const [dismissed, setDismissed] = useState(() => {
+    try {
+      return globalThis.sessionStorage?.getItem(DISMISSED_KEY) === "true";
+    } catch {
+      return false;
+    }
+  });
 
   if (dismissed) {
     return null;

Based on learnings: flag storage calls only when nothing handles thrown exceptions.

Committable suggestion

IMPORTANT
Review the replacement and indentation, then test before committing.

Suggested change
const [dismissed, setDismissed] = useState(
() => globalThis.sessionStorage?.getItem(DISMISSED_KEY) === "true",
);
const [dismissed, setDismissed] = useState(() => {
try {
return globalThis.sessionStorage?.getItem(DISMISSED_KEY) === "true";
} catch {
return false;
}
});

Source: Learnings

return (
<Ad
onClose={() => {
sessionStorage.setItem(DISMISSED_KEY, "true");

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Minor · Stability & Availability · Quick win

Handle a storage failure in the close handler.

sessionStorage.setItem throws when storage is blocked or full (for example, SecurityError or QuotaExceededError). The throw happens inside the click handler, so setDismissed(true) never runs. The banner then cannot be closed. The read at Line 64 is unguarded as well. Reading sessionStorage can throw when storage access is blocked.

Wrap both accesses in try/catch. Always call setDismissed(true).

Proposed fix

--- "a/src/components/CarbonAd/TopBar.jsx"
+++ "b/src/components/CarbonAd/TopBar.jsx"
@@ -70,10 +70,14 @@
 
   return (
     <Ad
       onClose={() => {
-        sessionStorage.setItem(DISMISSED_KEY, "true");
+        try {
+          sessionStorage.setItem(DISMISSED_KEY, "true");
+        } catch {
+          // Storage is unavailable. Dismiss for this mount only.
+        }
         setDismissed(true);
       }}
     />
   );
 }

Based on learnings: flag storage calls only when nothing handles thrown exceptions.

Committable suggestion

IMPORTANT
Review the replacement and indentation, then test before committing.

Suggested change
sessionStorage.setItem(DISMISSED_KEY, "true");
try {
sessionStorage.setItem(DISMISSED_KEY, "true");
} catch {
// Storage is unavailable. Dismiss for this mount only.
}

Source: Learnings

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Duplicate comments: 1

src/components/CarbonAd/TopBar.jsx: 1

63-65: Minor · Stability & Availability · Quick win

Handle sessionStorage failures.

sessionStorage access can throw SecurityError when storage is blocked. It can throw QuotaExceededError on write. The read at Line 64 runs during render and can break Page. The write at Line 74 throws before setDismissed(true) runs, so the user cannot close the banner. Wrap both calls in try/catch. Always call setDismissed(true).

Proposed fix

-  const [dismissed, setDismissed] = useState(
-    () => globalThis.sessionStorage?.getItem(DISMISSED_KEY) === "true",
-  );
+  const [dismissed, setDismissed] = useState(() => {
+    try {
+      return globalThis.sessionStorage?.getItem(DISMISSED_KEY) === "true";
+    } catch {
+      return false;
+    }
+  });
...
       onClose={() => {
-        sessionStorage.setItem(DISMISSED_KEY, "true");
+        try {
+          sessionStorage.setItem(DISMISSED_KEY, "true");
+        } catch {
+          // Storage is unavailable. Dismiss for this mount only.
+        }
         setDismissed(true);
       }}

Also applies to: 74-74

Source: Learnings

Review info

Run configuration

  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 3974126b-84ab-4eb8-b88a-eceff2ec82cb
Commits

Reviewing files that changed from the base of the PR and between 9cfb83c and 7b4b2a4.

Files selected for processing: 8

  • cspell.json
  • eslint.config.mjs
  • src/components/CarbonAd/SideBar.jsx
  • src/components/CarbonAd/TopBar.jsx
  • src/components/CarbonAd/carbon-ad.css
  • src/components/CarbonAd/useCarbonAd.mjs
  • src/components/Page/Page.jsx
  • src/components/Sidebar/Sidebar.jsx

Included review availability: This review used your included allowance. Your plan provides up to 8 included reviews per hour; 6 remain after this review.

* rest of the visit and does not request another ad.
*/
function Ad({ onClose }) {
const { ad, ref } = useCarbonAd("CWBI623E");

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Minor · Functional Correctness · Quick win

Restrict the top-bar ad to md and wider.

useCarbonAd("CWBI623E") has no media argument. Phone-width visitors therefore request and see the banner. The PR states that ads are requested only at md and above. Pass "(min-width: 768px)" as the second argument.

This also removes the phone-width request and impression. A viewport that narrows after the fetch still shows the banner. Hide it with a CSS max-md:hidden class if that matters.

Proposed fix

--- "a/src/components/CarbonAd/TopBar.jsx"
+++ "b/src/components/CarbonAd/TopBar.jsx"
@@ -11,7 +11,7 @@
  * rest of the visit and does not request another ad.
  */
 function Ad({ onClose }) {
-  const { ad, ref } = useCarbonAd("CWBI623E");
+  const { ad, ref } = useCarbonAd("CWBI623E", "(min-width: 768px)");
 
   if (!ad) {
     return null;
Committable suggestion

IMPORTANT
Review the replacement and indentation, then test before committing.

Suggested change
const { ad, ref } = useCarbonAd("CWBI623E");
const { ad, ref } = useCarbonAd("CWBI623E", "(min-width: 768px)");

* rest of the visit and does not request another ad.
*/
function Ad({ onClose }) {
const { ad, ref } = useCarbonAd("CWBI623E");

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Move the argument (ad key) to a higher level variable

This branch was successfully deployed

1 active deployment
Preview — 7b4b2a4f Deployed Nov 10, 2026 by vercel[bot]
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.

2 participants