[Bug] Docsite: pull-to-refresh is disabled on mobile by a global overscroll-behavior-y rule
- Dominant language
- TypeScript
- Stars
- 13.1k
- Forks
- 1.1k
- Avg merge
- 1d 14h
- Merged PRs (30d)
- 669
Description
## Description
Pull-to-refresh does not work anywhere on the docsite on mobile. Swipe down at the top of any page — home, a component page, the blog — and nothing happens.
The cause is one line in `apps/docsite/src/app/globals.css`:
```css
/* Disable overscroll bounce so the home hero's position:fixed layers can't
show through past the top/bottom of the page. */
html {
overscroll-behavior-y: none;
}
```
`overscroll-behavior-y: none` on the root element is the standard way to turn pull-to-refresh off. The rule is unscoped, so it applies to every route.
It is the only cause. There is no `touch-action` on `html`/`body`, no `overflow: hidden` or `position: fixed` body, and the document really is the scroller (`(site)/layout`, `blog/layout` and `DocsShell` all use `AppShell height="auto"`). `playground/` and `not-found` use `height="fill"` and have an inner scroller — no pull-to-refresh there regardless, which is separate and intentional.
### Why the line exists
#3032 ([`f4784f367`](https://github.com/facebook/astryx/commit/f4784f367ec521c1d3c38f514f41d5317e9f2198)). The landing page pins the hero band, nav backdrop, aurora glow and floating cards with `position: fixed` and scrolls the showcase over them; elastic overscroll at the top/bottom lifted the in-flow content and exposed those layers behind the footer.
The side effect looks unintentional. That PR reasons about the `(site)` route group but the rule landed in app-global `globals.css`; the bug was reported on desktop ("`overscroll-behavior` reliably stops the bounce on desktop browsers (where this was reported)"); and pull-to-refresh is not mentioned in its title, summary, four-item test plan or its single review. Its own Notes section names the real alternative: "bounding the fixed hero layers to the hero region (larger change to the tuned pin-and-cover)".
### What we should do
**Remove `overscroll-behavior-y: none` entirely, at all widths, and contain the fixed hero layers instead.** Suppressing the platform's native overscroll is a global behavioural change made to work around one page's paint bug; it costs pull-to-refresh on mobile and native rubber-band feel on macOS, and it will keep costing them for anything else added to the docsite later.
This splits cleanly into two steps, and the mobile one is small.
#### Step 1 — mobile (small, verified)
Below 1024px only **one** fixed layer survives:
| layer | below 1024px |
|---|---|
| `heroContent` (`(site)/page.tsx`) | `relative` — not pinned |
| `HeroFloatingCards.stage` | `display: none` |
| `HeroThemeReel.navBackdrop` | fixed, but a 48px strip at the very top — never in the bottom overscroll gap |
| **`HeroThemeReel.backdropGlow`** | **`position: fixed`, `top: header-height`, height 1050px, `display: block` at all widths** |
On a 714px viewport the glow spans y=48–1098 for the entire page scroll. That is the whole mobile bug. With the rule overridden to `auto`, the strip under the footer goes from `#ffffff` at rest to `#fcf4e7 / #fbe4ea / #fce6f2` mid-overscroll — the three aurora blobs.
Bounding it mirrors the pattern `heroContent` already uses ~100 lines away in the same file:
```ts
// HeroThemeReel.tsx — backdropGlow
position: {default: 'absolute', '@media (min-width: 1024px)': 'fixed'},
top: {default: 0, '@media (min-width: 1024px)': 'var(--appshell-header-height, 0px)'},
```
`top` has to drop to `0` in the absolute case: `heroScope` already starts at document y=48, below the header, so keeping the header offset shifts the glow down 48px (measured — absolute put it at y=96 vs fixed's 48). `heroScope` is `position: relative` and its comment already says it exists to bound the pin-and-cover, so it is the right offsetParent. The glow then scrolls away with the hero and can never reach the footer.
#### Step 2 — desktop (the real work)
At ≥1024px three layers are genuinely pinned — `heroContent`, `stage` and `backdropGlow` — and their fixedness *is* the pin-and-cover effect, so they cannot simply be made absolute. Options worth exploring: `position: sticky` inside `heroScope` (which already wraps hero + showcase and reserves height via `heroSpacer`), or a containment/clip boundary on the hero scope. This needs a careful visual pass on the tuned effect.
macOS trackpads do rubber-band, in Safari, Chrome and Firefox alike, so this step is not cosmetic — it is the case #3032 was actually filed for. Windows and Linux have no vertical bounce, so the rule is already a no-op there.
If step 2 needs to wait, the intermediate state after step 1 is:
```css
@media (min-width: 1024px) {
html { overscroll-behavior-y: none; }
}
```
That restores pull-to-refresh on 100% of the mobile site with no visual regression, and leaves desktop exactly as it is today. It should be a stepping stone, not the destination.
### Dead end, so nobody re-walks it
Extending an opaque slab below the footer (`box-shadow: 0 100vh 0 var(--color-background-surface)` on `.footer`, which is already `position: relative; z-index: 1`) does **not** work. AppShell's `.astryx-layout-content` has `overflow: clip`, so the shadow is clipped away — confirmed applied in computed style (`rgb(255,255,255) 0px 754px 0px 0px`) with the glow still bleeding. Any "paint past the end of the document" trick inside AppShell fails the same way.
## Reproduction
1. Open https://astryx.atmeta.com/components/Button on a phone (or the iOS Simulator).
2. With the page at the top, swipe down.
3. Nothing happens — no refresh spinner, no bounce. Expected: the page reloads.
To see the bug the rule was protecting against, override it to `auto`, scroll the home page to the very bottom and rubber-band past the footer: the aurora glow shows in the gap.
Measured on a real iOS 26.5 Safari simulator (iPhone 17 Pro) by proxying the live site and injecting CSS overrides. Pull-to-refresh detected via `performance.getEntriesByType('navigation')[0].type === 'reload'`; overscroll exposure by sampling screenshot pixels in the strip under the footer.
| arm | docs page PTR | home PTR | home bottom-overscroll strip |
|---|---|---|---|
| live today | no | no | clean (bounce suppressed) |
| blanket `auto` | yes | yes | **aurora glow bleeds** |
| glow bounded + `auto` | **yes** | **yes** | **clean** |
## Astryx Version
Docsite as deployed at https://astryx.atmeta.com (`apps/docsite`, `main`)
## Environment
iOS 26.5 Safari (iPhone 17 Pro simulator). Applies to any touch browser; Chrome Android suppresses pull-to-refresh from the same property.
Contributor guide
Research direction
Start with apps/docsite/src/app/globals.css and HeroThemeReel.tsx, then reproduce pull-to-refresh and bottom overscroll on the mobile docsite. Review heroScope, heroContent, HeroFloatingCards.stage, and HeroThemeReel.backdropGlow to preserve the desktop pin-and-cover effect while ensuring mobile refresh works and the aurora does not bleed below the footer.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- css, typescript
- Domain
- frontend, web-dev
- Issue type
- Bug
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 45/100