Skip to content

docs: keep banner height in sync via ResizeObserver#112

Merged
jdx merged 1 commit intomainfrom
feat/banner-resize-observer
Apr 23, 2026
Merged

docs: keep banner height in sync via ResizeObserver#112
jdx merged 1 commit intomainfrom
feat/banner-resize-observer

Conversation

@jdx
Copy link
Copy Markdown
Owner

@jdx jdx commented Apr 23, 2026

Summary

Follow-up fix for the banner.

`--vp-layout-top-height` was set once in a single rAF and never updated. On window resize, orientation change, or text reflow the banner height could change, leaving VitePress's 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 on dismiss.

\U0001F916 Generated with Claude Code


Note

Low Risk
Low risk docs-theme tweak that only affects banner layout; potential issues are limited to browsers without ResizeObserver support or unexpected observer behavior.

Overview
Keeps VitePress’s --vp-layout-top-height CSS var in sync with the announcement banner’s actual height by introducing a syncHeight helper and observing the banner with ResizeObserver.

On dismiss, the observer is disconnected and the layout height var is removed, replacing the previous one-time requestAnimationFrame height set with ongoing updates on resize/reflow.

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

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) <[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 (5eaf571) to head (6393ae0).
⚠️ Report is 1 commits behind head on main.

Additional details and impacted files
@@           Coverage Diff           @@
##             main     #112   +/-   ##
=======================================
  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

Replaces the one-shot requestAnimationFrame height sync with a ResizeObserver that continuously keeps --vp-layout-top-height aligned to the banner's rendered size, preventing nav overlap on resize or text reflow. The observer is disconnected on dismiss and the original rAF is retained as a fallback for environments without ResizeObserver.

Confidence Score: 5/5

Safe to merge — the fix is correct, the observer lifecycle is properly managed, and the only finding is a minor P2 optimization.

No P0 or P1 issues. The logic is sound: observer is set up after the element is in the DOM, disconnected on dismiss, and guarded against environments without ResizeObserver. The sole comment is a P2 suggestion to avoid a forced reflow inside the callback by using the entry's borderBoxSize.

No files require special attention.

Important Files Changed

Filename Overview
docs/.vitepress/theme/banner.ts Adds ResizeObserver to keep --vp-layout-top-height in sync with the banner's rendered height; observer is cleanly disconnected on dismiss. One minor P2: el.offsetHeight in the observer callback forces an unnecessary layout reflow.

Sequence Diagram

sequenceDiagram
    participant initBanner
    participant render
    participant DOM
    participant rAF
    participant ResizeObserver
    participant CSSVar as --vp-layout-top-height

    initBanner->>render: fetch() resolves, banner not dismissed
    render->>DOM: document.body.prepend(el)
    render->>rAF: requestAnimationFrame(syncHeight)
    render->>ResizeObserver: observer.observe(el)
    rAF-->>CSSVar: syncHeight() → set height (initial)
    ResizeObserver-->>CSSVar: syncHeight() on first observation
    Note over ResizeObserver,CSSVar: Re-fires on window resize / text reflow
    ResizeObserver-->>CSSVar: syncHeight() → update height

    alt User clicks Dismiss
        DOM->>ResizeObserver: observer.disconnect()
        DOM->>CSSVar: removeProperty(--vp-layout-top-height)
        DOM->>DOM: el.remove()
    end
Loading

Fix All in Claude Code

Reviews (1): Last reviewed commit: "docs: keep --vp-layout-top-height in syn..." | Re-trigger Greptile

Comment on lines +54 to +58
const syncHeight = () => {
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.

P2 Forced reflow in ResizeObserver callback

el.offsetHeight triggers a synchronous layout calculation every time the observer fires. Inside a ResizeObserver callback the browser already provides the new dimensions via entries, so you can read them without forcing an extra reflow.

Suggested change
const syncHeight = () => {
document.documentElement.style.setProperty(
"--vp-layout-top-height",
`${el.offsetHeight}px`,
);
const syncHeight = (entries?: ResizeObserverEntry[]) => {
const height =
entries?.[0]?.borderBoxSize?.[0]?.blockSize ?? el.offsetHeight;
document.documentElement.style.setProperty(
"--vp-layout-top-height",
`${height}px`,
);
};

The borderBoxSize path is what you actually want (border-box matches offsetHeight semantics) and is available in all browsers that support ResizeObserver. The ?? el.offsetHeight fallback keeps the initial requestAnimationFrame call working when called without entries.

Fix in Claude Code

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 dynamic height synchronization for the banner component using a ResizeObserver. The review feedback identifies opportunities to prevent a race condition when the banner is dismissed, improve measurement precision using getBoundingClientRect, and eliminate a redundant requestAnimationFrame call when ResizeObserver is supported.

Comment on lines +54 to +59
const syncHeight = () => {
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

There are two improvements for the syncHeight function:

  1. Race Condition: Since syncHeight is called via requestAnimationFrame, it might execute after the banner has been dismissed and removed from the DOM. In this case, el.offsetHeight will be 0, and the code will re-add the --vp-layout-top-height property as 0px even though it was explicitly removed in the click handler. Adding an el.isConnected check prevents this.
  2. Precision: el.offsetHeight returns an integer. For layout-sensitive variables like this, using el.getBoundingClientRect().height is preferred as it provides sub-pixel precision, avoiding potential 1px gaps or overlaps on high-DPI screens.
Suggested change
const syncHeight = () => {
document.documentElement.style.setProperty(
"--vp-layout-top-height",
`${el.offsetHeight}px`,
);
};
const syncHeight = () => {
if (!el.isConnected) return;
const height = el.getBoundingClientRect().height;
document.documentElement.style.setProperty(
"--vp-layout-top-height",
height + "px",
);
};

Comment on lines +80 to +81
requestAnimationFrame(syncHeight);
observer?.observe(el);
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

When ResizeObserver is supported, observer.observe(el) will trigger an initial notification, making the requestAnimationFrame(syncHeight) call redundant. You can wrap the rAF in an else block to only use it as a fallback for older browsers.

  if (observer) {
    observer.observe(el);
  } else {
    requestAnimationFrame(syncHeight);
  }

@jdx jdx merged commit 21267b3 into main Apr 23, 2026
9 checks passed
@jdx jdx deleted the feat/banner-resize-observer branch April 23, 2026 19:49
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