# Unify the Viewer Header for Markdown and HTML Pages

## Problem

The viewer currently renders two intentionally different shell headers:

- **Markdown pages** use a centered document header constrained to `52rem`, with a large `1.75rem` title above the version and theme controls.
- **HTML pages** use a compact, fixed, full-width `3rem` toolbar with a `1rem` ellipsized title, version and theme controls, and an **Open fullscreen** action.

This split came from the full-screen HTML viewer design, which preserved Markdown as a traditional readable document. The result is that navigation and viewer-owned controls look like two different products even though both page formats belong to the same viewer.

This is a shell inconsistency, not a Markdown rendering or theme bug.

## Desired Outcome

Use one compact top toolbar for both Markdown and HTML pages while preserving their different content behavior:

- Markdown remains a centered, normally scrolling readable document.
- HTML remains a full-viewport, sandboxed iframe.
- HTML alone retains the **Open fullscreen** action.
- Version selection, theme selection, title styling, focus states, and responsive behavior look consistent across formats.
- While reading Markdown, the toolbar fades and slides out when scrolling down, then reappears when scrolling up.

## Important Scrolling Constraint

The hide-on-scroll behavior will apply to **Markdown pages**.

HTML artifacts scroll inside a sandboxed iframe with an opaque origin. The trusted outer viewer cannot inspect that iframe's scroll position or direction. Attempting to do so would require weakening the iframe sandbox or introducing a cooperation protocol with published content, both of which conflict with the current security model.

The HTML toolbar therefore remains visible and fixed. This preserves the existing sandbox boundary and keeps fullscreen and version controls available. The plan must not use wheel-event interception, sandbox relaxation, or injected code as workarounds.

## Implementation Plan

### 1. Create a shared toolbar partial

Add `app/templates/viewer/_toolbar.html` containing:

- Page title.
- Shared version-menu partial.
- Shared theme-menu partial.
- Optional fullscreen action when a fullscreen URL is present.

Keep the toolbar server-rendered and semantically structured with `<header>`, `<h1>`, and `<nav>` elements.

### 2. Adopt the shared toolbar in both viewer templates

Update `app/templates/viewer/html_host.html` to render the shared partial without changing its iframe structure.

Update `app/templates/viewer/markdown.html` to:

- Add a Markdown-specific body class such as `viewer-markdown-host`.
- Replace the large `.viewer-header` and `.viewer-title` block with the shared toolbar.
- Keep the existing `.viewer-main`, `.viewer-content`, and `.markdown-body` structure.
- Omit the fullscreen URL so Markdown does not display **Open fullscreen**.
- Load the trusted scroll-behavior asset only on Markdown pages.

This also reduces the prominent duplicate-heading effect when the page title and the first Markdown `# heading` contain similar text.

### 3. Refactor viewer CSS around shared chrome

In `app/static/viewer.css`:

- Make toolbar height, typography, border, spacing, title ellipsis, action alignment, dropdown placement, hover styles, and focus styles common to both formats.
- Keep the toolbar visually identical across formats.
- Let the Markdown toolbar be sticky and remain in normal document flow.
- Preserve fixed positioning and viewport-height calculations for the HTML host.
- Retain the Markdown content width of `52rem`, content card, padding, typography, and normal scrolling.
- Remove obsolete `.viewer-header`, `.viewer-header-controls`, and `.viewer-title` rules after the templates stop using them.

### 4. Add Markdown scroll-direction behavior

Add a small trusted static asset such as `app/static/viewer-scroll.js`. It should run only when `body.viewer-markdown-host` is present.

Behavior:

- Start with the toolbar visible.
- Keep it visible while the page is near the top.
- After a deliberate downward-scroll threshold, add a state class such as `.viewer-toolbar-hidden`.
- Fade and translate the toolbar upward using CSS, rather than removing it from layout.
- Reveal it promptly when upward scrolling passes a smaller threshold.
- Use hysteresis or accumulated scroll distance so trackpad noise and tiny direction changes do not make the toolbar flicker.
- Use a passive scroll listener and `requestAnimationFrame` so scroll handling does not block rendering.
- Keep the toolbar visible whenever it contains keyboard focus or either the version/theme `<details>` menu is open.
- Re-evaluate state on `pageshow`, anchor navigation, and viewport changes.
- If JavaScript fails or is blocked, degrade to an ordinary always-visible sticky toolbar.

Suggested motion is a short 160–220 ms opacity and transform transition. Under `prefers-reduced-motion: reduce`, remove the animation; the toolbar may still switch visibility immediately.

### 5. Keep the CSP change narrow

The current shell CSP uses `script-src 'none'`. Reliable scroll-direction detection is not broadly achievable with CSS alone, so Markdown shell pages need permission to load the trusted external script.

Update the viewer response policy so:

- Markdown shell pages use `script-src 'self'`.
- The script is external, content-fingerprinted, and loaded with `defer`; do not allow inline scripts or `'unsafe-inline'`.
- HTML host pages may retain `script-src 'none'` because their toolbar does not react to iframe scrolling.
- Raw published HTML must continue using `script-src 'none'`.
- No published content is ever able to call or modify the trusted shell script.

Add a content hash for the JavaScript asset in the same spirit as the existing viewer CSS cache-busting hash.

### 6. Handle responsive layouts explicitly

Verify the shared toolbar at narrow widths:

- The title shrinks first and uses single-line ellipsis.
- Version, theme, and fullscreen controls remain usable.
- Dropdown panels remain within the viewport and open from the appropriate edge.
- No controls wrap over or overlap the content.
- Keyboard focus rings remain visible.
- Revealing the toolbar does not cause layout shift.

If necessary, shorten or selectively hide nonessential action labels at the smallest breakpoint without introducing additional client state.

### 7. Update automated tests

Update `tests/test_viewer_smoke.py`:

- Replace the assertion that Markdown must not contain `.viewer-toolbar`.
- Verify Markdown renders the shared toolbar and retains the content card.
- Verify Markdown does not render the fullscreen action.
- Verify only Markdown includes the scroll-behavior asset.

Update `tests/test_viewer_security.py`:

- Preserve all HTML iframe, sandbox, immutable raw URL, and safe new-tab assertions.
- Verify the HTML host continues to provide the fullscreen action.
- Verify raw HTML retains `script-src 'none'`.
- Verify Markdown permits only same-origin shell scripts, with no inline-script allowance.
- Verify HTML hosts do not need broader script permission.

Update `tests/test_viewer_css.py` and theme tests:

- Verify shared toolbar styling and body-specific positioning.
- Cover hidden/visible state classes, transition rules, and reduced-motion behavior.
- Cover long-title ellipsis and bounded menus.
- Verify System, Light, and Dark selections work from both formats.
- Confirm no iframe or raw HTML palette behavior changes.

Add focused JavaScript behavior tests using a browser-capable test harness, or isolate the scroll-state calculation into a testable function and cover:

- Near-top visibility.
- Downward threshold.
- Upward reveal threshold.
- Jitter resistance.
- Open-menu and focused-control visibility.
- Back-forward restoration.

Run the complete pytest suite and perform browser-based visual QA.

### 8. Update documentation

Update `README.md`, `docs/architecture.md`, and `MEMORY.md` to explain:

- Markdown and HTML share viewer-owned toolbar chrome.
- Their content layouts remain intentionally different.
- Markdown hides/reveals the toolbar according to outer-document scroll direction.
- The HTML toolbar cannot respond to sandboxed iframe scrolling and stays visible.
- Published HTML still controls its own palette inside the sandbox.
- The previous decision that Markdown would not adopt the toolbar has been superseded.
- Trusted shell JavaScript is narrowly allowed on Markdown pages while published HTML remains script-blocked.

Add a note to `docs/full-screen-html-viewer-plan.md` or link to a new decision entry so the historical document no longer appears to describe current intended behavior.

## Security and Compatibility Constraints

The change must not alter:

- REST or MCP schemas.
- Public or versioned viewer URLs.
- Markdown sanitization.
- HTML iframe sandboxing.
- Raw HTML CSP.
- Immutable version pinning.
- Theme-cookie behavior.
- Raw HTML fidelity.

The shell CSP change is limited to the same-origin Markdown viewer script. It must not enable scripts in published HTML.

## Acceptance Criteria

- Markdown and HTML pages display the same compact top toolbar.
- Title, version, and theme controls use identical typography and alignment.
- On Markdown pages, deliberate downward scrolling fades/slides the toolbar away.
- On Markdown pages, upward scrolling reveals the toolbar promptly.
- The toolbar remains visible near the top, while focused, and while a menu is open.
- Tiny scroll-direction changes do not cause flicker.
- Reduced-motion preferences are respected.
- Without JavaScript, Markdown falls back to an always-visible sticky toolbar.
- Markdown remains centered, readable, and normally scrollable.
- HTML remains a borderless full-viewport sandboxed iframe with an always-visible toolbar.
- **Open fullscreen** appears only for HTML pages.
- Long titles and controls remain usable on mobile widths.
- Light, Dark, and System themes render correctly.
- Raw published HTML remains unable to execute scripts.
- All existing security guarantees and the complete automated test suite pass.
