Skip to content

docs: add cross-site announcement banner#109

Merged
jdx merged 3 commits intomainfrom
feat/site-banner
Apr 23, 2026
Merged

docs: add cross-site announcement banner#109
jdx merged 3 commits intomainfrom
feat/site-banner

Conversation

@jdx
Copy link
Copy Markdown
Owner

@jdx jdx commented Apr 23, 2026

Summary

  • Adds docs/.vitepress/theme/banner.ts + banner.css — a small module that fetches banner config from https://jdx.dev/banner.json and renders a dismissible announcement bar at the top of the docs
  • Wires it in by extending DefaultTheme with an enhanceApp hook
  • Dismissals persist per banner id in localStorage; bumping the id in the source JSON re-shows it to everyone

Used to announce en.dev, and any future cross-site announcements.

Test plan

  • Run docs dev server, confirm banner appears at top of page
  • Click the \u00d7 \u2014 banner disappears and stays dismissed across reloads
  • Clear localStorage jdx-banner-dismissed, reload \u2014 banner returns

\U0001F916 Generated with Claude Code


Note

Medium Risk
Adds client-side runtime code to the docs site that fetches remote JSON and injects DOM/CSS, which could affect layout/availability if the endpoint is slow/unavailable or serves unexpected content.

Overview
Adds a dismissible, fixed top announcement banner to the VitePress docs theme, driven by a remote https://jdx.dev/banner.json config and persisted per-banner via localStorage.

Wires the banner initialization into the theme’s enhanceApp hook and adjusts layout by setting/clearing --vp-layout-top-height based on the rendered banner height.

Reviewed by Cursor Bugbot for commit 2c70cbc. Bugbot is set up for automated code reviews on this repo. Configure here.

Fetches banner config from https://jdx.dev/banner.json and renders a
dismissible top-of-page announcement. Dismissals persist per banner id
in localStorage, so bumping the id re-shows it.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 23, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 94.08%. Comparing base (1585680) to head (2c70cbc).
⚠️ Report is 1 commits behind head on main.

Additional details and impacted files
@@           Coverage Diff           @@
##             main     #109   +/-   ##
=======================================
  Coverage   94.08%   94.08%           
=======================================
  Files          26       26           
  Lines        4055     4055           
  Branches     4055     4055           
=======================================
  Hits         3815     3815           
  Misses        155      155           
  Partials       85       85           

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

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented Apr 23, 2026

Greptile Summary

Adds a dismissible cross-site announcement banner to the VitePress docs theme, driven by a remote JSON endpoint at https://jdx.dev/banner.json and persisted per-banner-id via localStorage. The previous security concerns (URL scheme injection, missing noreferrer) have been addressed in this revision.

Confidence Score: 5/5

Safe to merge — all prior security issues are resolved and remaining findings are P2 style suggestions

The two open comments are both P2: one is a defensive guard against a malformed id field in the developer-controlled JSON, and the other is a ResizeObserver enhancement for layout accuracy on resize. Neither blocks correct operation under normal usage.

docs/.vitepress/theme/banner.ts — minor runtime validation and resize handling improvements worth considering

Important Files Changed

Filename Overview
docs/.vitepress/theme/banner.ts New module that fetches remote banner JSON, validates URLs with isHttpUrl, and renders a dismissible bar; previous security issues addressed, two minor P2 concerns remain (no id field guard, no resize handling)
docs/.vitepress/theme/banner.css New stylesheet for the fixed-position banner; straightforward and clean with responsive breakpoint
docs/.vitepress/theme/index.ts Wires banner into VitePress DefaultTheme via enhanceApp; minimal, correct change

Sequence Diagram

sequenceDiagram
    participant VP as VitePress enhanceApp
    participant B as banner.ts
    participant API as jdx.dev/banner.json
    participant LS as localStorage
    participant DOM as document.body

    VP->>B: initBanner()
    B->>B: check typeof window
    B->>API: fetch(ENDPOINT, {cache: 'no-cache'})
    API-->>B: BannerData { id, enabled, message, link? }
    B->>LS: getItem('jdx-banner-dismissed')
    LS-->>B: stored id or null
    alt not dismissed & enabled
        B->>DOM: prepend .jdx-banner div
        B->>DOM: setProperty(--vp-layout-top-height, px)
    end
    Note over B,DOM: User clicks ×
    B->>LS: setItem('jdx-banner-dismissed', b.id)
    B->>DOM: el.remove()
    B->>DOM: removeProperty(--vp-layout-top-height)
Loading

Fix All in Claude Code

Reviews (3): Last reviewed commit: "docs: fix banner double-offset by using ..." | Re-trigger Greptile

Comment thread docs/.vitepress/theme/banner.ts Outdated
Comment thread docs/.vitepress/theme/banner.ts Outdated
Comment thread docs/.vitepress/theme/banner.ts Outdated
Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request implements a dynamic announcement banner for the VitePress site, including its styling, fetching logic, and theme integration. The review feedback suggests changing the banner's position to fixed to maintain layout consistency during scrolling, wrapping localStorage calls in try-catch blocks to handle potential browser exceptions, and utilizing a ResizeObserver to ensure the layout offset remains accurate if the banner's height changes.

Comment thread docs/.vitepress/theme/banner.css Outdated
@@ -0,0 +1,37 @@
.jdx-banner {
position: relative;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

The banner is currently set to position: relative, but it sets the --vp-layout-top-height CSS variable which VitePress uses to offset the fixed navigation bar. When the user scrolls down, the banner will scroll out of view, but the navigation bar will remain fixed at an offset from the top of the viewport, leaving an empty gap at the top of the screen. Changing this to position: fixed ensures the banner stays at the top along with the navigation bar and maintains the correct layout during scroll.

Suggested change
position: relative;
position: fixed;
top: 0;
left: 0;
right: 0;

Comment thread docs/.vitepress/theme/banner.ts Outdated
btn.setAttribute('aria-label', 'Dismiss')
btn.textContent = '\u00d7'
btn.addEventListener('click', () => {
localStorage.setItem(STORAGE_KEY, b.id)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

Accessing localStorage.setItem can throw a DOMException in certain browser configurations (e.g., when third-party cookies or storage are blocked). If an error occurs here, the banner will not be removed from the DOM and the layout property won't be cleared, preventing the user from dismissing the banner. Wrapping this in a try...catch ensures the UI remains functional even if storage fails.

Suggested change
localStorage.setItem(STORAGE_KEY, b.id)
try { localStorage.setItem(STORAGE_KEY, b.id) } catch {}

Comment thread docs/.vitepress/theme/banner.ts Outdated
Comment on lines +58 to +63
requestAnimationFrame(() => {
document.documentElement.style.setProperty(
'--vp-layout-top-height',
`${el.offsetHeight}px`,
)
})
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

The banner height is only calculated once during the initial render. However, the height can change if the window is resized (e.g., due to the media query in banner.css or text wrapping on smaller screens). This will cause the navigation bar offset (--vp-layout-top-height) to become incorrect. Using a ResizeObserver ensures the layout stays synchronized with the actual height of the banner.

  const observer = new ResizeObserver(() => {
    document.documentElement.style.setProperty(
      '--vp-layout-top-height',
      `${el.offsetHeight}px`,
    )
  })
  observer.observe(el)

Only allow http(s): links from the upstream JSON. Defense-in-depth
against a compromised jdx.dev injecting a javascript: URL that would
execute arbitrary code in the docs origin on click.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 3a67bb7. Configure here.

Comment thread docs/.vitepress/theme/banner.css
Banner was using position: relative which put it in document flow
*and* VitePress applies --vp-layout-top-height offset, causing content
to be pushed down twice. Switch to position: fixed so the banner is
out of flow and --vp-layout-top-height alone handles the content
offset (which is what VitePress's layout-top slot assumes).

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
@jdx jdx merged commit eb49d11 into main Apr 23, 2026
9 checks passed
@jdx jdx deleted the feat/site-banner branch April 23, 2026 18:42
jdx added a commit that referenced this pull request Apr 23, 2026
Follow-ups to #109 that landed after the merge, plus the en.dev footer.

## Summary
### Banner polish
- **Contrast**: switch to dark text on the pink brand bg (white text
failed contrast)
- **z-index**: bump to 1001 so the banner stays above the site's nav
- **Centering**: message + link centered; dismiss button pinned to the
right via absolute positioning
- **Analytics**: drop `rel=noreferrer` so en.dev gets referrer
attribution. Keep `rel=noopener`.

### Footer
- Adds the en.dev footer matching the one on the mise docs, via
VitePress's `layout-bottom` slot

## Test plan
- [ ] Run docs dev server, confirm banner text is centered and readable
- [ ] Confirm en.dev footer appears at the bottom of pages
- [ ] Click the \u00d7 on the banner \u2014 dismisses and stays hidden
across reload

\U0001F916 Generated with [Claude Code](https://claude.com/claude-code)

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Low Risk**
> Low risk: docs-only theme/CSS tweaks plus a small `rel` attribute
change on an external link; no backend or data model impact.
> 
> **Overview**
> Polishes the docs site announcement banner styling to improve
readability and layout (dark text, centered content, higher `z-index`,
and an absolutely positioned dismiss button), with updated responsive
padding.
> 
> Adds a new `EndevFooter` component and wires it into VitePress via the
`layout-bottom` slot, and adjusts the banner link to use
`rel="noopener"` (dropping `noreferrer`).
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
151266a. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

---------

Co-authored-by: Claude Opus 4.7 (1M context) <[email protected]>
jdx added a commit that referenced this pull request Apr 23, 2026
A small patch release that fixes a panic when generating notes against
releases with multi-byte characters in their bodies, and picks up a
security fix in `rustls-webpki`.

## Fixed

- **Don't panic on multi-byte chars in style-reference bodies** —
`communique generate` truncates each recent release body to 3072 bytes
to keep the prompt small, but previously sliced `&body[..3072]`
directly. If byte 3072 fell inside a multi-byte UTF-8 character (common
with em-dashes, which are 3 bytes), the command would panic with `byte
index 3072 is not a char boundary`. The truncation now walks back to the
nearest char boundary before slicing, with a regression test covering
the case. ([#113](#113)) (@jdx)

## Security

- **`rustls-webpki` bumped to 0.103.13** — Addresses
[RUSTSEC-2026-0104](https://rustsec.org/advisories/RUSTSEC-2026-0104), a
reachable panic in certificate revocation list parsing. Lockfile-only
change. ([#107](#107)) (@jdx)

## Docs

- Added a dismissible cross-site announcement banner and an en.dev
footer to the documentation site, with follow-up polish (contrast,
centering, z-index), smarter caching, and `ResizeObserver`-based height
syncing so VitePress's nav offset stays correct on resize.
([#109](#109),
[#110](#110),
[#111](#111),
[#112](#112)) (@jdx)

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
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.

1 participant