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) <noreply@anthropic.com>
Greptile SummaryThis PR adds a dismissible cross-site announcement banner that fetches config from Confidence Score: 4/5Safe to merge after addressing two open findings from the prior review round still visible in the diff. Good progress on the prior review — protocol injection and ResizeObserver concerns are resolved. Two P1 findings from earlier rounds (missing noreferrer, unguarded localStorage.setItem in the click handler) are still present in the code. No new P1/P0 issues identified in this pass. docs/.vitepress/theme/banner.ts — the dismiss click handler and the anchor rel attribute. Important Files Changed
Sequence DiagramsequenceDiagram
participant Browser
participant VitePress as VitePress (enhanceApp)
participant BannerTS as banner.ts
participant Endpoint as jdx.dev/banner.json
participant LocalStorage
Browser->>VitePress: Page load / hydration
VitePress->>BannerTS: initBanner()
BannerTS->>Endpoint: fetch(ENDPOINT)
Endpoint-->>BannerTS: BannerData { id, enabled, message, link }
BannerTS->>LocalStorage: getItem("jdx-banner-dismissed")
LocalStorage-->>BannerTS: stored id or null
alt not dismissed & enabled
BannerTS->>Browser: render(b) → prepend .jdx-banner to body
BannerTS->>Browser: requestAnimationFrame(syncHeight)
BannerTS->>Browser: ResizeObserver.observe(el)
Browser-->>BannerTS: User clicks ×
BannerTS->>LocalStorage: setItem("jdx-banner-dismissed", b.id)
BannerTS->>Browser: el.remove() + removeProperty(--vp-layout-top-height)
else dismissed or disabled
BannerTS-->>Browser: no-op
end
Reviews (9): Last reviewed commit: "docs: keep --vp-layout-top-height in syn..." | Re-trigger Greptile |
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) <noreply@anthropic.com>
There was a problem hiding this comment.
Code Review
This pull request introduces a dynamic announcement banner system for the documentation site. It includes a new CSS file for styling, a TypeScript module to fetch banner data from a remote endpoint and manage its display, and integration into the VitePress theme initialization. The banner supports a dismissal feature that persists via localStorage. The review feedback suggests adding error handling around localStorage access to prevent potential runtime errors in environments where storage is blocked or full.
| .then((r) => (r.ok ? (r.json() as Promise<BannerData>) : null)) | ||
| .then((b) => { | ||
| if (!b || !b.enabled) return; | ||
| if (localStorage.getItem(STORAGE_KEY) === b.id) return; |
There was a problem hiding this comment.
Accessing localStorage can throw an error in some environments (e.g., if cookies/storage are blocked or in certain private browsing modes). It is safer to wrap this check in a try-catch block to ensure the banner logic doesn't crash the application initialization.
try {
if (localStorage.getItem(STORAGE_KEY) === b.id) return
} catch (e) {
// ignore storage errors
}| a.href = b.link; | ||
| a.target = "_blank"; | ||
| a.rel = "noopener noreferrer"; | ||
| a.textContent = b.linkText || "Learn more"; |
There was a problem hiding this comment.
Similar to the retrieval, setting an item in localStorage can throw an error if storage is disabled or full. Wrapping this in a try-catch ensures the dismissal functionality fails gracefully without impacting the rest of the UI.
try {
localStorage.setItem(STORAGE_KEY, b.id)
} catch (e) {
// ignore storage errors
}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) <noreply@anthropic.com>
- Bump z-index to 1001 so the banner sits above custom nav overrides (e.g. hk's .VPNav at z-index: 1000 !important). - Use dark text on light brand backgrounds (coral/pink/gold/cyan) for readable contrast. Sites with dark brand backgrounds keep white text. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds a small footer with license, copyright, and link back to en.dev, matching the footer used on the mise docs. Rendered via VitePress's layout-bottom slot. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Center message+link in the banner, position dismiss button absolutely at the right edge so it doesn't skew the centering. - Drop rel=noreferrer on the link so en.dev gets analytics attribution for traffic from the docs. Keep rel=noopener for security. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Left/right padding was asymmetric (1rem vs 2.75rem desktop; 0.75rem vs 2.5rem mobile), which shifted the "centered" text off from true viewport center. Match the sides so justify-content: center lines up with the viewport midpoint. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
no-cache forces a conditional GET on every page load. The server sends Cache-Control: public, max-age=300, must-revalidate, so default browser caching already gives 5-min freshness, which is plenty for an announcement banner. Returning users with a dismissed banner also already short-circuit via localStorage before the fetch runs anyway. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The previous fallback (#3451b2, VitePress default dark blue) made black banner text unreadable if --vp-c-brand-1 ever fails to resolve. Use a fallback that matches this site's actual brand color so black text stays readable in the fallback scenario. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The height CSS variable was set once in a single rAF and never updated afterward. On window resize, orientation change, or text reflow the banner height could change, leaving the VitePress nav offset stale and causing overlap or a visible gap. Observe the banner element and resync the variable whenever its size changes; disconnect the observer when the banner is dismissed. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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 622e8db. Configure here.
| observer?.disconnect(); | ||
| el.remove(); | ||
| document.documentElement.style.removeProperty("--vp-layout-top-height"); | ||
| }); |
There was a problem hiding this comment.
Unhandled localStorage.setItem exception blocks banner dismissal
Low Severity
The dismiss button's click handler calls localStorage.setItem as its first statement. If this throws (e.g., QuotaExceededError on a full storage, or storage disabled in certain browsing contexts), the remaining cleanup — observer?.disconnect(), el.remove(), and removeProperty("--vp-layout-top-height") — never executes. This leaves the banner permanently visible and undismissable for the session, since every click re-throws. Wrapping the setItem in a try-catch (or moving it after el.remove()) would let dismissal still work even when persistence fails.
Reviewed by Cursor Bugbot for commit 622e8db. Configure here.
### 🐛 Bug Fixes - **(git)** skip untracked scan when HK_STASH_UNTRACKED=false by [@jdx](https://github.com/jdx) in [#861](#861) - **(run)** add post-commit and pre-rebase subcommands by [@jdx](https://github.com/jdx) in [#858](#858) ### 📚 Documentation - **(install)** recommend global hooks as primary setup path by [@jdx](https://github.com/jdx) in [#855](#855) - add cross-site announcement banner by [@jdx](https://github.com/jdx) in [#857](#857) - respect banner expires field by [@jdx](https://github.com/jdx) in [#862](#862) ### 🔍 Other Changes - vendor bats test helpers instead of git submodules by [@jdx](https://github.com/jdx) in [#859](#859) ### 📦️ Dependency Updates - bump communique to 1.0.3 by [@jdx](https://github.com/jdx) in [#863](#863) - update anthropics/claude-code-action digest to e58dfa5 by [@renovate[bot]](https://github.com/renovate[bot]) in [#864](#864) <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > Low risk release bookkeeping only: version numbers, generated CLI/docs, and lockfile dependency bumps with no runtime logic changes. > > **Overview** > **Release prep for `v1.44.1`.** Updates `Cargo.toml`/`Cargo.lock` (including `libc`) and all version references in generated CLI docs (`docs/cli/*`, `hk.usage.kdl`). > > Refreshes documentation examples to reference the new `v1.44.1` Pkl package URLs and adds the `1.44.1` entry to `CHANGELOG.md`. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit d7637d4. 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: mise-en-dev <123107610+mise-en-dev@users.noreply.github.com>
This MR contains the following updates: | Package | Update | Change | |---|---|---| | [hk](https://github.com/jdx/hk) | minor | `1.43.0` → `1.45.0` | MR created with the help of [el-capitano/tools/renovate-bot](https://gitlab.com/el-capitano/tools/renovate-bot). **Proposed changes to behavior should be submitted there as MRs.** --- ### Release Notes <details> <summary>jdx/hk (hk)</summary> ### [`v1.45.0`](https://github.com/jdx/hk/blob/HEAD/CHANGELOG.md#1450---2026-05-04) [Compare Source](jdx/hk@v1.44.3...v1.45.0) ##### 🚀 Features - **(builtins)** add `buildifier` format and lint built-ins by [@​plx](https://github.com/plx) in [#​896](jdx/hk#896) ##### 🐛 Bug Fixes - **(step)** only auto-batch when rendered command exceeds ARG\_MAX by [@​jdx](https://github.com/jdx) in [#​901](jdx/hk#901) ##### 📚 Documentation - thank Namespace for GitHub Actions runner support by [@​jdx](https://github.com/jdx) in [#​895](jdx/hk#895) ##### 🔍 Other Changes - **(ci)** use !cancelled() instead of always() for final job by [@​jdx](https://github.com/jdx) in [#​906](jdx/hk#906) - **(docs)** remove shrill.en.dev analytics script by [@​jdx](https://github.com/jdx) in [#​903](jdx/hk#903) - remove rust-cache from release jobs by [@​jdx](https://github.com/jdx) in [#​893](jdx/hk#893) - invert CLAUDE.md/AGENTS.md so AGENTS.md is canonical by [@​jdx](https://github.com/jdx) in [#​905](jdx/hk#905) - set dev profile debug to 1 by [@​jdx](https://github.com/jdx) in [#​907](jdx/hk#907) ##### 📦️ Dependency Updates - update anthropics/claude-code-action digest to [`fefa07e`](jdx/hk@fefa07e) by [@​renovate\[bot\]](https://github.com/renovate\[bot]) in [#​897](jdx/hk#897) - update jdx/mise-action digest to [`1648a78`](jdx/hk@1648a78) by [@​renovate\[bot\]](https://github.com/renovate\[bot]) in [#​898](jdx/hk#898) - update apple-actions/import-codesign-certs action to v7 by [@​renovate\[bot\]](https://github.com/renovate\[bot]) in [#​900](jdx/hk#900) - update autofix-ci/action action to v1.3.4 by [@​renovate\[bot\]](https://github.com/renovate\[bot]) in [#​899](jdx/hk#899) - lock file maintenance by [@​renovate\[bot\]](https://github.com/renovate\[bot]) in [#​908](jdx/hk#908) ##### New Contributors - [@​plx](https://github.com/plx) made their first contribution in [#​896](jdx/hk#896) ### [`v1.44.3`](https://github.com/jdx/hk/blob/HEAD/CHANGELOG.md#1443---2026-04-30) [Compare Source](jdx/hk@v1.44.2...v1.44.3) ##### 🐛 Bug Fixes - **(hook)** do not stage fixes when fail\_on\_fix=true by [@​jdx](https://github.com/jdx) in [#​892](jdx/hk#892) - use site domain for plausible data-domain by [@​jdx](https://github.com/jdx) in [#​886](jdx/hk#886) - make text-mode progress output usable in CI by [@​jdx](https://github.com/jdx) in [#​890](jdx/hk#890) ##### 📚 Documentation - prefix GitHub star count with ★ glyph by [@​jdx](https://github.com/jdx) in [#​883](jdx/hk#883) ##### 🔍 Other Changes - **(release)** dedupe sponsor section in release notes by [@​jdx](https://github.com/jdx) in [#​881](jdx/hk#881) - switch analytics from gtm/goatcounter to plausible by [@​jdx](https://github.com/jdx) in [#​885](jdx/hk#885) - migrate to namespace.so runners by [@​jdx](https://github.com/jdx) in [#​891](jdx/hk#891) ### [`v1.44.2`](https://github.com/jdx/hk/blob/HEAD/CHANGELOG.md#1442---2026-04-26) [Compare Source](jdx/hk@v1.44.1...v1.44.2) ##### 🐛 Bug Fixes - **(builtins)** silence pklr deprecation warnings on Builtins.pkl load by [@​jdx](https://github.com/jdx) in [#​880](jdx/hk#880) - **(ci)** serialize docs lint step by [@​jdx](https://github.com/jdx) in [#​874](jdx/hk#874) - **(config)** include main pkl path in cache fresh files by [@​jdx](https://github.com/jdx) in [#​879](jdx/hk#879) - **(docs)** stack banner message and link on mobile by [@​jdx](https://github.com/jdx) in [#​865](jdx/hk#865) - **(docs)** pin banner close button to top-right corner on mobile by [@​jdx](https://github.com/jdx) in [#​867](jdx/hk#867) ##### 📚 Documentation - **(site)** show release version and github stars by [@​jdx](https://github.com/jdx) in [#​872](jdx/hk#872) ##### 🔍 Other Changes - add pr-closer workflow by [@​jdx](https://github.com/jdx) in [#​876](jdx/hk#876) ##### 📦️ Dependency Updates - bump communique 1.0.3 → 1.0.4 by [@​jdx](https://github.com/jdx) in [#​868](jdx/hk#868) - update anthropics/claude-code-action digest to [`2da6cfa`](jdx/hk@2da6cfa) by [@​renovate\[bot\]](https://github.com/renovate\[bot]) in [#​869](jdx/hk#869) - update anthropics/claude-code-action digest to [`567fe95`](jdx/hk@567fe95) by [@​renovate\[bot\]](https://github.com/renovate\[bot]) in [#​870](jdx/hk#870) - bump communique to 1.1.2 by [@​jdx](https://github.com/jdx) in [#​875](jdx/hk#875) ### [`v1.44.1`](https://github.com/jdx/hk/blob/HEAD/CHANGELOG.md#1441---2026-04-24) [Compare Source](jdx/hk@v1.44.0...v1.44.1) ##### 🐛 Bug Fixes - **(git)** skip untracked scan when HK\_STASH\_UNTRACKED=false by [@​jdx](https://github.com/jdx) in [#​861](jdx/hk#861) - **(run)** add post-commit and pre-rebase subcommands by [@​jdx](https://github.com/jdx) in [#​858](jdx/hk#858) ##### 📚 Documentation - **(install)** recommend global hooks as primary setup path by [@​jdx](https://github.com/jdx) in [#​855](jdx/hk#855) - add cross-site announcement banner by [@​jdx](https://github.com/jdx) in [#​857](jdx/hk#857) - respect banner expires field by [@​jdx](https://github.com/jdx) in [#​862](jdx/hk#862) ##### 🔍 Other Changes - vendor bats test helpers instead of git submodules by [@​jdx](https://github.com/jdx) in [#​859](jdx/hk#859) ##### 📦️ Dependency Updates - bump communique to 1.0.3 by [@​jdx](https://github.com/jdx) in [#​863](jdx/hk#863) - update anthropics/claude-code-action digest to [`e58dfa5`](jdx/hk@e58dfa5) by [@​renovate\[bot\]](https://github.com/renovate\[bot]) in [#​864](jdx/hk#864) ### [`v1.44.0`](https://github.com/jdx/hk/blob/HEAD/CHANGELOG.md#1440---2026-04-23) [Compare Source](jdx/hk@v1.43.0...v1.44.0) ##### 🚀 Features - **(check)** implement --plan, --why, and --json by [@​jdx](https://github.com/jdx) in [#​848](jdx/hk#848) - **(cocogitto)** add cocogitto conventional commits config to hk builtin config by [@​hituzi-no-sippo](https://github.com/hituzi-no-sippo) in [#​838](jdx/hk#838) - **(git)** support GIT\_DIR/GIT\_WORK\_TREE for bare-repo dotfile managers by [@​jdx](https://github.com/jdx) in [#​847](jdx/hk#847) - **(install)** use Git 2.54 config-based hooks with --global support by [@​jdx](https://github.com/jdx) in [#​853](jdx/hk#853) ##### 🐛 Bug Fixes - use text progress in CI by [@​jdx](https://github.com/jdx) in [#​845](jdx/hk#845) ##### 📚 Documentation - generalize agent guidelines by [@​jdx](https://github.com/jdx) in [#​846](jdx/hk#846) - add releases nav and aube lock by [@​jdx](https://github.com/jdx) in [#​849](jdx/hk#849) ##### 🔍 Other Changes - **(release)** append en.dev sponsor blurb to release notes by [@​jdx](https://github.com/jdx) in [#​854](jdx/hk#854) - bump communique to 1.0.1 by [@​jdx](https://github.com/jdx) in [#​850](jdx/hk#850) ##### 📦️ Dependency Updates - update actions-rust-lang/setup-rust-toolchain digest to [`2b1f5e9`](jdx/hk@2b1f5e9) by [@​renovate\[bot\]](https://github.com/renovate\[bot]) in [#​832](jdx/hk#832) - update anthropics/claude-code-action digest to [`c3d45e8`](jdx/hk@c3d45e8) by [@​renovate\[bot\]](https://github.com/renovate\[bot]) in [#​833](jdx/hk#833) - update rust crate tokio to v1.52.1 by [@​renovate\[bot\]](https://github.com/renovate\[bot]) in [#​834](jdx/hk#834) - update actions/upload-pages-artifact action to v5 by [@​renovate\[bot\]](https://github.com/renovate\[bot]) in [#​835](jdx/hk#835) - update taiki-e/upload-rust-binary-action digest to [`f0d45ae`](jdx/hk@f0d45ae) by [@​renovate\[bot\]](https://github.com/renovate\[bot]) in [#​839](jdx/hk#839) - update rust crate clx to v2 by [@​renovate\[bot\]](https://github.com/renovate\[bot]) in [#​836](jdx/hk#836) - update anthropics/claude-code-action digest to [`0d2971c`](jdx/hk@0d2971c) by [@​renovate\[bot\]](https://github.com/renovate\[bot]) in [#​841](jdx/hk#841) - update anthropics/claude-code-action digest to [`38ec876`](jdx/hk@38ec876) by [@​renovate\[bot\]](https://github.com/renovate\[bot]) in [#​842](jdx/hk#842) - lock file maintenance by [@​renovate\[bot\]](https://github.com/renovate\[bot]) in [#​851](jdx/hk#851) </details> --- ### Configuration 📅 **Schedule**: (UTC) - Branch creation - At any time (no schedule defined) - Automerge - At any time (no schedule defined) 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Whenever MR becomes conflicted, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this MR and you won't be reminded about this update again. --- - [ ] <!-- rebase-check -->If you want to rebase/retry this MR, check this box --- This MR has been generated by [Mend Renovate](https://github.com/renovatebot/renovate). <!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4xNjguNSIsInVwZGF0ZWRJblZlciI6IjQzLjE2OC41IiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJSZW5vdmF0ZSBCb3QiLCJhdXRvbWF0aW9uOmJvdC1hdXRob3JlZCIsImRlcGVuZGVuY3ktdHlwZTo6bWlub3IiXX0=-->


Summary
docs/.vitepress/theme/banner.ts+banner.css\u2014 fetches banner config fromhttps://jdx.dev/banner.jsonand renders a dismissible announcement bar at the top of the docsenhanceApphooklocalStorage; bumping the id re-shows itUsed 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 DOM injection that fetches remote banner content and persists dismissal in
localStorage, which can affect layout and introduces a new external dependency for docs pages.Overview
Introduces a cross-site, dismissible announcement banner for the docs theme:
initBanner()fetches config fromhttps://jdx.dev/banner.json, renders a fixed top bar with optional outbound link, adjusts--vp-layout-top-height, and stores per-banner dismissals inlocalStorage.Adds a new
EndevFootercomponent and wires it intoLayout.vuevia thelayout-bottomslot to display license/copyright and anen.devlink.Reviewed by Cursor Bugbot for commit 622e8db. Bugbot is set up for automated code reviews on this repo. Configure here.