From 69b5deef194f7f86cab7f6b1f221bb36b990c8e5 Mon Sep 17 00:00:00 2001 From: riyadhafraa <49757212-riyadhafraa@users.noreply.replit.com> Date: Fri, 1 May 2026 09:23:36 +0000 Subject: [PATCH] #267 freeze top bar, schedule heading, and table column header on scroll MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Executive Meetings page: keep the page header, the schedule title row (heading + edit toggle + date picker), and the table column header visible while the user scrolls a long meetings list. Implementation: - ExecutiveMeetingsPage publishes the live header height as a CSS variable `--em-header-h` on the page root (data-em-root) via a ResizeObserver. The
is now `sticky top-0 z-40` with print:hidden so the print layout is untouched. - ScheduleSection wraps the title row in a `sticky top:var(--em-header-h) z-30` div with a soft full-bleed background and bottom shadow, and publishes its own height as `--em-heading-h` on the page root via a second ResizeObserver. Cleanup resets the var to "0px" so other sections without a heading don't inherit a stale offset. - SortableHeader's is now `position:sticky` with `top: calc(var(--em-header-h) + var(--em-heading-h)); zIndex:5` and a solid `bg-[#0B1E3F]` so scrolled rows don't bleed through. - The table wrapper changes from `overflow-x-auto` to `overflow-x-auto xl:overflow-x-visible` so at >=xl the inner thead's nearest scrolling ancestor is the viewport (sticky works). Below xl the wrapper retains horizontal scroll and the column header gracefully degrades to non-sticky; the page header + heading row still stick at every width. - Print mode is preserved via `print:hidden` (sticky bars) and `print:!static` / `print:!shadow-none` overrides (sticky headers). Tests: - New e2e spec executive-meetings-sticky-header.spec.mjs covers three scenarios: desktop LTR (all three layers stick flush), Arabic RTL (header + heading stick), and narrow viewport (header + heading stick, column header documented to degrade). - Re-ran related schedule e2e specs (bulk-actions 5/5, edit-toggle 6/6, schedule-features 4/4) and the full API suite (226/226 sequential) — all pass. New testids: `em-page-header`, `em-schedule-heading-bar`. Existing `em-schedule-heading` testid retained on the inner h2. No drift from task plan. --- .../tx-os/src/pages/executive-meetings.tsx | 82 ++++- .../executive-meetings-sticky-header.spec.mjs | 288 ++++++++++++++++++ 2 files changed, 366 insertions(+), 4 deletions(-) create mode 100644 artifacts/tx-os/tests/executive-meetings-sticky-header.spec.mjs diff --git a/artifacts/tx-os/src/pages/executive-meetings.tsx b/artifacts/tx-os/src/pages/executive-meetings.tsx index 4b29b3ad..92afcddc 100644 --- a/artifacts/tx-os/src/pages/executive-meetings.tsx +++ b/artifacts/tx-os/src/pages/executive-meetings.tsx @@ -552,12 +552,44 @@ export default function ExecutiveMeetingsPage() { const meetings = data?.meetings ?? []; + // #267: Measure the header so the in-section sticky rows (page title + + // table thead) can sit flush below it on every breakpoint. The header + // wraps on narrow widths, so a static Tailwind `top-N` would either + // gap or overlap. We expose the live height as `--em-header-h` on the + // page root and let descendant sticky elements read it via `top: + // var(--em-header-h)`. + const rootRef = useRef(null); + const headerRef = useRef(null); + useEffect(() => { + const header = headerRef.current; + const root = rootRef.current; + if (!header || !root) return; + const update = () => { + root.style.setProperty("--em-header-h", `${header.offsetHeight}px`); + }; + update(); + const ro = new ResizeObserver(update); + ro.observe(header); + window.addEventListener("resize", update); + return () => { + ro.disconnect(); + window.removeEventListener("resize", update); + }; + }, []); + return (
-
+