-
Analysis
Current structure on issue detail (same pattern on build / PR detail):
.card.issue-detail .card-header .card-title a.more-info.side-info <!-- Element B --> .card-body.d-flex .main …operations / primary… ul.tabs.nav-tabs-sticky <!-- Element A: position:sticky; top:0; z-index:990 --> …tab content… div.side-info <!-- SideInfoPanel -->Sticky on
.tabsworks because it lives inside the scrolling.main/.autofitchain. Sticky on.more-infocannot work while it stays under.card-header: once the header scrolls off, sticky is clipped by that ancestor. Existing CSS already hides triggers when the pane is open (.side-info-visible a.side-info { display: none }), and PR summary links already proxy-click the header trigger (pull-request-detail.js).Tabbablealready has a right-aligned.optionsslot (margin-left: auto), but it is owned by the selected tab’srenderOptions()— not a good place for a page-level more-info control without API changes.
1. Technical strategy (recommended)
Recommended: sticky tabs row wrapper + second
SideInfoLink+IntersectionObserverApproach Pros Cons A. Sticky row + docked SideInfoLink(recommended)Keeps header link in place for default layout; docked link is a real Wicket SideInfoLink(Tippy / Ajax /SideInfoOpenedstay intact); layout reserved via flex on the sticky row; noposition:fixedmathSmall markup change (move sticky from ulto a thin wrapper)B. position:fixedoverlay toggled by observerZero DOM moves Must sync top/right/height to tabs on scroll/resize; easy to drift vs side pane / dark mode blur bar C. Reparent / clone the header <a>into tabsVisually exact Fragile with Wicket Ajax partial updates; Tippy/event rebind risk Why A: Default state keeps Element B in
.card-headerand Element A as the tab list. Only when the header trigger leaves the scrollport do we show a second trigger that already lives inside the sticky row, so it docks attop: 0beside the tabs with no fixed-position bookkeeping.
2. Step-by-step implementation
Step 1 — Markup (e.g.
IssueDetailPage.html)Wrap tabs + docked trigger; move sticky to the wrapper so both stick together. Keep the original header link unchanged:
<div class="card-header d-flex align-items-center flex-nowrap"> <div wicket:id="title" class="card-title flex-grow-1 mx-0"></div> <a wicket:id="moreInfo" class="more-info side-info flex-shrink-0 ml-3" t:data-tippy-content="More info">…</a> </div> … <div class="sticky-tabs-row d-flex align-items-center mb-5"> <ul wicket:id="issueTabs" class="tabs nav nav-tabs nav-tabs-line nav-bold flex-grow-1 mb-0"></ul> <a wicket:id="moreInfoDock" class="more-info side-info more-info-dock flex-shrink-0 ml-3" t:data-tippy-content="More info">…</a> </div>- Remove
nav-tabs-stickyfrom theul(sticky moves to the row). - Default:
.more-info-dockis hidden (see CSS), so the unscrolled layout matches today.
Mirror on
BuildDetailPage/PullRequestDetailPage(same header + sticky tabs pattern).Step 2 — Java
Add a second
SideInfoLink("moreInfoDock")next to the existingSideInfoLink("moreInfo"). Both broadcastSideInfoOpened; no click proxying required (unlike PR summary text links).Step 3 — CSS (
base.cssor page CSS).sticky-tabs-row { position: sticky; top: 0; z-index: 990; /* reuse existing sticky tab chrome */ background: rgba(255, 255, 255, 0.88); -webkit-backdrop-filter: blur(8px); backdrop-filter: blur(8px); } .dark-mode .sticky-tabs-row { background: rgba(35, 35, 45, 0.88); } /* hidden until docked — preserves default layout */ .sticky-tabs-row > .more-info-dock { display: none; } .sticky-tabs-row.is-docked > .more-info-dock { display: inline-flex; } /* keep existing: body.side-info-visible a.side-info { display: none } */ html:has(.revision-diff) .sticky-tabs-row { position: static; /* same exception as today’s sticky tabs on PR changes */ }Optionally move the existing
.nav-tabs-line.nav-tabs-stickyrules onto.sticky-tabs-rowso all sticky-tab pages share one definition.Step 4 — JS (
IntersectionObserver)Observe the header trigger against the real scroll root (the nearest scrolling ancestor, typically the
.autofitscroller — notviewportalone):onedev.server.sideInfo.dockMoreInfoWithStickyTabs = function(options) { var $root = $(options.root); var header = $root.find(options.headerTrigger)[0]; var $row = $root.find(options.stickyRow); if (!header || !$row.length) return; var scrollRoot = $row.closest(".autofit")[0] || null; var observer = new IntersectionObserver(function(entries) { var visible = entries[0].isIntersecting; $row.toggleClass("is-docked", !visible); }, { root: scrollRoot, threshold: 0 }); observer.observe(header); };Call from page
onDomReady(and after Ajax refreshes that replace the header/tabs).Stuck detection: “header more-info not intersecting” ≈ “tabs have reached sticky” for this layout, because the header sits above the tabs. If pixel-perfect stick detection is needed, observe a 1px sentinel placed immediately above
.sticky-tabs-rowinstead.Step 5 — Collision / alignment
- Row is
display: flex; tabsflex-grow-1; dockflex-shrink-0+ml-3— same spacing as the header control. - Tabs keep wrapping (
flex-wrapon theulif needed); dock stays on the right of the row. - No
padding-righthack required because the dock participates in flex layout only while.is-dockedis on (when hidden viadisplay: noneit takes no space).
Step 6 — Transitions & edge cases
Concern Handling Tippy / Ajax on docked control Real second SideInfoLink— same as headerSide pane open Existing .side-info-visible a.side-info { display: none }hides bothPR changes / revision diff position: staticexception on the row (same as current sticky tabs)Ajax tab body updates Observer targets header + row outside tab child content; re-init only if those nodes are replaced Smoothness Toggle class only (no DOM move). Optional short opacity transition on .more-info-dockif desired; avoid animating layout width of the tab list if it feels jumpyMobile overlay side info Unchanged — docked link still sends SideInfoOpened
3. Proposed code touch points (diff outline)
No code landed in this answer (Question). Implementation would touch roughly:
IssueDetailPage.html(+ Build/PR detail HTML) — sticky row +moreInfoDockIssueDetailPage.java(+ Build/PR) —new SideInfoLink("moreInfoDock")base.css—.sticky-tabs-row/.is-docked(lift styles off.nav-tabs-sticky)side-info.js— shareddockMoreInfoWithStickyTabsissue-detail.js/pull-request-detail.js/ build detail JS — call helper fromonDomReady
If this plan looks right, we can open an implementation issue / convert follow-up work from this. If you want a different approach (fixed overlay, or always-visible dock in the tab bar), say which and we can adjust.
- Remove
-
Previous Value Current Value Dock Header Action Button Alongside Tabs Only When Sticky
Question: Dock Header Action Button Alongside Tabs Only When Sticky
-
-
See #2969 for implementation
-
Previous Value Current Value Open
Closed
| Type |
Question
|
| Priority |
Normal
|
| Assignee | |
| Labels |
No labels
|
Problem Statement
Currently:
.tabs): Located inside.card-body > .main.more-info): Located inside.card-headerWhen scrolling down:
.tabsusesposition: stickyand sticks totop: 0..more-infoscrolls out of view because its parent container (.card-header) leaves the viewport.Requirement
.tabsreachestop: 0and becomes sticky,.more-infoshould dynamically dock attop: 0alongside.tabs(sitting on the right side) so both are visible side-by-side without overlapping.Target Elements
Element A (Tabs Bar):
Element B (More Info Button):
Motivation
The
.more-infobutton, when the side info panel is hidden, currently scrolls out of view when the user scrolls down the page. This means that in order to access the.side-infopane, the user needs to scroll all the way to the top. However, as we have done in a past issue, we moved the Workspaces UI element from the top into the.side-infopane in order to not need to scroll all the way up to the top in the first place.Therefore, what we want to do is be able to expose the
.side-infopanel without needing to scroll all the way up to the top. This makes it easier for users to open and close the side info panel using the.more-infobutton no matter where they have scrolled in the page.Tasks for the AI Agent Plan
Please analyze the page DOM structure and provide a step-by-step implementation plan covering:
position: stickyon.more-infowill stop working as soon as.card-headerscrolls off-screen.IntersectionObserver/ Scroll listener toggling a CSS class like.is-docked, usingposition: fixed, or dynamic DOM reparenting/cloning) to retain.more-infoat the top next to.tabsonly during sticky scroll..tabsreserves space on the right (e.g.,padding-rightor flex layout) so.more-infodocks cleanly next to it without covering tab text or buttons..more-info(e.g., Tippy.js tooltips or dropdown handlers).Expected Output