Mobile: fixed-width popovers (clone menu, notification tray) overflow the viewport #278

Closed
opened 2026-09-10 11:19:50 +00:00 by crueber · 3 comments
Owner

What's wrong

Fixed-width absolutely-positioned popovers overflow the phone viewport when triggered near the right edge (which is where all the header popovers live):

  • Clone menu: .clone-body is absolute right-0 … w-96 (384px) — at a 390px viewport its left edge lands at x = −10 (measured live): the panel hangs off the left side of the screen and its content is clipped.
  • Notification tray: .tray measures 384px wide opening from a trigger already clipped by the header overflow (#273) — right: 515 measured on repo pages, fully off-screen on one side.

Both are unusable on mobile: content cut off on one edge, no way to see the whole panel.

Measured evidence (390×844)

clone-body:  left:-10, w:384 (page docW 531 on repo pages — the popover contributes)
tray:        w:384, right:515 on repo pages

Root cause (code evidence)

  • web/css/repo.css:90-95 (.clone-menu/.clone-body): position: absolute; … width implied by Tailwind w-96atweb/src/pages/Repo.jsx:92 (class="clone-body card absolute right-0 z-30 mt-2 w-96 space-y-3 p-3"`). No max-width bound against the viewport.
  • The tray panel is the same shape (fixed ~384px, anchored right).
  • The branch/reviewer pickers already solved a version of this with #236/#237 popover work — but width-vs-viewport wasn't part of it.

Fix direction

  • Bound right-anchored popovers to the viewport: max-width: calc(100vw - 16px) (or Tailwind max-w-[calc(100vw-1rem)]) on .clone-body, .tray, and an audit of the other absolute panels (ref-drop is 320px — fits at 390 but barely; tasks popover, theme-adjacent panels).
  • Alternatively anchor them with left: 0; right: 0; margin: auto at small breakpoints so they span the viewport minus padding.
  • Acceptance: at 390px, opening Clone from the repo header and the notification tray shows the full panel within the viewport (both edges visible); no page-level horizontal scroll contributed by any open popover.
## What's wrong Fixed-width absolutely-positioned popovers overflow the phone viewport when triggered near the right edge (which is where all the header popovers live): - **Clone menu**: `.clone-body` is `absolute right-0 … w-96` (384px) — at a 390px viewport its left edge lands at **x = −10** (measured live): the panel hangs off the left side of the screen and its content is clipped. - **Notification tray**: `.tray` measures 384px wide opening from a trigger already clipped by the header overflow (#273) — `right: 515` measured on repo pages, fully off-screen on one side. Both are unusable on mobile: content cut off on one edge, no way to see the whole panel. ## Measured evidence (390×844) ``` clone-body: left:-10, w:384 (page docW 531 on repo pages — the popover contributes) tray: w:384, right:515 on repo pages ``` ## Root cause (code evidence) - `web/css/repo.css:90-95` (`.clone-menu`/`.clone-body`): `position: absolute; … width implied by Tailwind `w-96` at `web/src/pages/Repo.jsx:92` (`class="clone-body card absolute right-0 z-30 mt-2 w-96 space-y-3 p-3"`). No max-width bound against the viewport. - The tray panel is the same shape (fixed ~384px, anchored right). - The branch/reviewer pickers already solved a version of this with #236/#237 popover work — but width-vs-viewport wasn't part of it. ## Fix direction - Bound right-anchored popovers to the viewport: `max-width: calc(100vw - 16px)` (or Tailwind `max-w-[calc(100vw-1rem)]`) on `.clone-body`, `.tray`, and an audit of the other `absolute` panels (`ref-drop` is 320px — fits at 390 but barely; tasks popover, theme-adjacent panels). - Alternatively anchor them with `left: 0; right: 0; margin: auto` at small breakpoints so they span the viewport minus padding. - Acceptance: at 390px, opening Clone from the repo header and the notification tray shows the full panel within the viewport (both edges visible); no page-level horizontal scroll contributed by any open popover.
Author
Owner

Fixed by #290 (branch fix/issue-278): shared max-width: calc(100vw - 1rem) bound on all audited popovers in web/src/ui.css + regression test. Ready for review — not merging per task rules.

Fixed by #290 (branch fix/issue-278): shared max-width: calc(100vw - 1rem) bound on all audited popovers in web/src/ui.css + regression test. Ready for review — not merging per task rules.
Author
Owner

Review of PR #290 (fix/issue-278), verified in scratch worktree /tmp/pr290 (removed after):

RE-AUDIT (absolute/fixed panels in web/src): all 10 bound panels confirmed — clone-body (Repo.jsx:99, right-0 w-96), ref-drop (Repo.jsx:289 left-0 w-80; Pull.jsx:220 + PullNew.jsx:82 w-full), tasks-drop (Repo.jsx:453 right-0 w-96), notif-drop (NotificationTray.jsx:98 right-0 w-80), reaction-drop (ReactionMenu.jsx:116 left-0), label-drop (LabelPicker.jsx:92 right-0 w-64), milestone-drop (MilestonePicker.jsx:73, same shape as label-drop), close-drop (CommentComposer.jsx:92 bottom-full right-0 min-w-52), tag-drop (ReleaseNew.jsx:214 inset-x-0), tray (ui.css:157 fixed bottom-4 right-4 w-96). No misses. Deliberately unbounded and correct: .progress (inset-x-0, already fluid), notif badge (tiny), sha.jsx clipboard textarea (invisible), FinishReview form (inline, not floating).

FINDINGS:

  1. Bound rule correct — web/src/ui.css max-width: calc(100vw-1rem) caps only; no width utilities touched in JSX; desktop w-96/w-80/w-64 preserved (pinned by test). Theme-independent (no color tokens in the rule). No new deps (diff = ui.css + one test file).
  2. milestone-drop opaque addition sound — MilestonePicker.jsx:73 class string is byte-identical in shape to LabelPicker.jsx:92; joining the #115 solid-panel rule is consistent, no JSX churn.
  3. Missing doc entry (AGENTS.md law 12: #115 entry in docs/go/12_web_ui.md:542 listed the opaque scope without milestone-drop; the new bound had no entry). FIXED directly: appended the #278 FIXED entry to docs/go/12_web_ui.md, committed 235db75 and pushed to origin/fix/issue-278.

VERIFY (scratch only, main untouched): node --test web/test/unit/*.test.js 557/557 green incl. new popover-viewport.test.js 5/5 (scratch has no node_modules; used a read-only symlink to main's — marked-dependent suites fail without it, environmental only, 0 fail with it). vite build green in 2s; built CSS contains max-width:calc(100vw-1rem). (Chunk-size warning is pre-existing.) No browser run — no runnable browser in this environment; proof is structural (selector + 390px fit arithmetic), noted explicitly.

MERGE RECOMMENDATION: ready to merge (not merging per task rules).

Review of PR #290 (fix/issue-278), verified in scratch worktree /tmp/pr290 (removed after): RE-AUDIT (absolute/fixed panels in web/src): all 10 bound panels confirmed — clone-body (Repo.jsx:99, right-0 w-96), ref-drop (Repo.jsx:289 left-0 w-80; Pull.jsx:220 + PullNew.jsx:82 w-full), tasks-drop (Repo.jsx:453 right-0 w-96), notif-drop (NotificationTray.jsx:98 right-0 w-80), reaction-drop (ReactionMenu.jsx:116 left-0), label-drop (LabelPicker.jsx:92 right-0 w-64), milestone-drop (MilestonePicker.jsx:73, same shape as label-drop), close-drop (CommentComposer.jsx:92 bottom-full right-0 min-w-52), tag-drop (ReleaseNew.jsx:214 inset-x-0), tray (ui.css:157 fixed bottom-4 right-4 w-96). No misses. Deliberately unbounded and correct: .progress (inset-x-0, already fluid), notif badge (tiny), sha.jsx clipboard textarea (invisible), FinishReview form (inline, not floating). FINDINGS: 1. Bound rule correct — web/src/ui.css max-width: calc(100vw-1rem) caps only; no width utilities touched in JSX; desktop w-96/w-80/w-64 preserved (pinned by test). Theme-independent (no color tokens in the rule). No new deps (diff = ui.css + one test file). 2. milestone-drop opaque addition sound — MilestonePicker.jsx:73 class string is byte-identical in shape to LabelPicker.jsx:92; joining the #115 solid-panel rule is consistent, no JSX churn. 3. Missing doc entry (AGENTS.md law 12: #115 entry in docs/go/12_web_ui.md:542 listed the opaque scope without milestone-drop; the new bound had no entry). FIXED directly: appended the #278 FIXED entry to docs/go/12_web_ui.md, committed 235db75 and pushed to origin/fix/issue-278. VERIFY (scratch only, main untouched): node --test web/test/unit/*.test.js 557/557 green incl. new popover-viewport.test.js 5/5 (scratch has no node_modules; used a read-only symlink to main's — marked-dependent suites fail without it, environmental only, 0 fail with it). vite build green in 2s; built CSS contains max-width:calc(100vw-1rem). (Chunk-size warning is pre-existing.) No browser run — no runnable browser in this environment; proof is structural (selector + 390px fit arithmetic), noted explicitly. MERGE RECOMMENDATION: ready to merge (not merging per task rules).
Author
Owner

Fixed by PR #290 incl. review doc entry (all 10 panels bounded, milestone-drop opaque; 557/557), merged. Closing.

Fixed by PR #290 incl. review doc entry (all 10 panels bounded, milestone-drop opaque; 557/557), merged. Closing.
crueber added this to the v1 milestone 2026-09-10 22:27:08 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
crueber/walhub#278
No description provided.