# Phase 60 — Sticky Navbar (stays stuck to the top while scrolling) **Source:** `TODO.md` L3 — "The navbar disappears when you scroll down, should stay stuck to the top of the screen" **Story:** n/a (TODO-derived — owner roadmap confirmation 2026-08-31, A1–A3) **Context:** The navbar already HAS the sticky CSS: `.app-header` (`frontend/assets/styles.css` ~L194 — `position: sticky; top: 0; z-index: 20`) and the document viewer's two-row `.doc-header` (~L2201 — also `position: sticky; top: 0`), both direct children of `` on every page. The bug: `html, body { height: 100% }` (~L40) pins the body box to exactly one viewport, and a sticky element's travel range is constrained to its containing block — so after ~1 viewport of scrolling the header un-pins and scrolls away with the body. Verified with headless Chromium during the TODO audit (2026-08-31): with the current rule the header's rect top is −1264px after a 2000px scroll; with `height` dropped from `body` (keeping `min-height: 100dvh`) it is top = 0, and on a short page the footer still lands at the viewport bottom (800/800 at 1280×800). The document must stay the scroll container (phase 52 no-inner-scroller contract, ~L387) — this fix adds no scroller, it only lets the body grow to its content. `.agent/` is untracked (owner commit 281f355) — phase commits stage `frontend/ tests/` only. ## Objective The navbar — and the document-viewer header — stays stuck to the top of the screen on every page at every scroll position, while the short-page layout (flex stretch, pinned composer, footer) stays exactly as it is today. ## Dependencies - `59_response_to_docs_push` (todo, preceding — no functional dependency; ordering by number) ## Tasks 1. `01_sticky_header_css.md` — drop `height: 100%` from `body` (keep it on `html`), refresh the stale phase-12 comment, unit CSS pins in `tests/unit/test_sticky_header.py`. 2. `02_e2e_sticky_navbar.md` — `tests/e2e/test_sticky_navbar.py` (rect-top == 0 proofs on Sources + viewer at the bottom of long pages; short-page stretch/composer regressions), regression suites, atomic commit. ## Testing & Quality - Unit CSS pins (house style, `tests/unit/test_save_chat_ui.py` pattern): `html` keeps `height: 100%`; the `body` rule carries NO `height:` (negative pin) and keeps `min-height: 100dvh`; `.app-header` and `.doc-header` keep `position: sticky; top: 0`. - E2E (mandatory, A3): `tests/e2e/test_sticky_navbar.py`, run in isolation. - Coverage: **>90%** on `app/` (validate.sh gate — pure-CSS phase, gate still runs). ## Completion Criteria - [ ] Sources page (KB long enough to scroll), 1280×800: after `window.scrollTo(0, 999999)` the `.app-header` rect top == 0 (±1px) and the header is visible. - [ ] Document viewer on a long doc: after full scroll, `.doc-header` rect top == 0 (±1px). - [ ] Short page (empty chat, 1280×800): body height == 800, `.app-footer` bottom == 800, `.composer` bottom ≈ 800 (±2px) — the phase-52 pin holds. - [ ] `uv run pytest` green; coverage TOTAL >90%. - [ ] `uv run pytest tests/e2e/test_sticky_navbar.py -v --no-cov` green in isolation (DB up). - [ ] Regression E2E suites green in isolation: `test_pinned_composer.py`, `test_document_viewer.py`, `test_responsive_polish.py`, `test_smoke.py`, `test_no_reply_autoscroll.py`, `test_mobile_hamburger_nav.py`. - [ ] `uv run ruff check . && uv run pyright` clean. - [ ] One `--no-gpg-sign` commit; phase dir moved to `.agent/phases/complete/` (`.agent/` stays untracked — owner instruction, commit 281f355). ## Locked decisions - **Owner-locked (2026-08-31, roadmap confirmation, A1–A3):** 1. **A1 (root cause):** the sticky travel range is capped by `body { height: 100% }` (`styles.css` L40) — the body box is pinned to one viewport and sticky elements (`.app-header`, `.doc-header`) can only travel within it. 2. **A2 (fix):** CSS-only — drop `height: 100%` from `body` (keep it on `html`); the body's existing `min-height: 100dvh` keeps driving the short-page stretch. No JS changes, no new scroller (the document stays the scroll container). 3. **A3 (E2E):** long-scroll surfaces = the seeded Sources table + the document viewer, with KB seeding via `import_sources` (the `test_document_viewer.py` pattern — fixtures + generated docs). No real LLM involved. ## Commit ```bash git add frontend/ tests/ && git commit --no-gpg-sign -m "fix(web): keep the navbar stuck to the top — drop the body height cap on the sticky range" ```