Conversation
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 Report✅ All modified and coverable lines are covered by tests. 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. 🚀 New features to boost your workflow:
|
Greptile SummaryAdds a dismissible cross-site announcement banner to the VitePress docs theme, driven by a remote JSON endpoint at Confidence Score: 5/5Safe 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
Sequence DiagramsequenceDiagram
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)
Reviews (3): Last reviewed commit: "docs: fix banner double-offset by using ..." | Re-trigger Greptile |
There was a problem hiding this comment.
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.
| @@ -0,0 +1,37 @@ | |||
| .jdx-banner { | |||
| position: relative; | |||
There was a problem hiding this comment.
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.
| position: relative; | |
| position: fixed; | |
| top: 0; | |
| left: 0; | |
| right: 0; |
| btn.setAttribute('aria-label', 'Dismiss') | ||
| btn.textContent = '\u00d7' | ||
| btn.addEventListener('click', () => { | ||
| localStorage.setItem(STORAGE_KEY, b.id) |
There was a problem hiding this comment.
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.
| localStorage.setItem(STORAGE_KEY, b.id) | |
| try { localStorage.setItem(STORAGE_KEY, b.id) } catch {} |
| requestAnimationFrame(() => { | ||
| document.documentElement.style.setProperty( | ||
| '--vp-layout-top-height', | ||
| `${el.offsetHeight}px`, | ||
| ) | ||
| }) |
There was a problem hiding this comment.
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]>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ 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.
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]>
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]>
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>

Summary
docs/.vitepress/theme/banner.ts+banner.css— a small module that fetches banner config fromhttps://jdx.dev/banner.jsonand renders a dismissible announcement bar at the top of the docsDefaultThemewith anenhanceApphooklocalStorage; bumping the id in the source JSON re-shows it to everyoneUsed to announce en.dev, and any future cross-site announcements.
Test plan
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.jsonconfig and persisted per-banner vialocalStorage.Wires the banner initialization into the theme’s
enhanceApphook and adjusts layout by setting/clearing--vp-layout-top-heightbased 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.