# Sidebar Layout Fix Report

**Date:** May 31, 2026  
**Scope:** App shell scroll containment, sticky sidebar rail, profile footer anchoring, mobile drawer breakpoint

---

## 1. Root cause

The authenticated layout used a **document-level scroll model** instead of an app-shell scroll model:

- `LayoutContainer` used `min-height: 100vh` with no height cap, so the page could grow and scroll on `body`.
- `SidebarContainer` was `position: fixed` with `overflow-y: auto` on the **entire** sidebar, so nav and footer scrolled together and clipped against the viewport during main-content scroll.
- `AppMain` offset the main column with `margin-inline-start` while the sidebar lived outside the flex/grid flow, which made sticky/fixed behavior fragile when the outer page scrolled.
- `ContentArea` had `overflow-y: auto` but its parent (`AppMain`) did not constrain height (`min-height: 0` / `overflow: hidden`), so scroll often fell through to `body` — producing **double-scroll** and sidebar movement/clipping on pages like `/tickets`.

The production bundle had partially moved toward a column shell (header + row), but source still used the older fixed-sidebar + margin pattern without a dedicated nav scroll region or anchored footer.

---

## 2. Layout architecture

### Before

```
LayoutContainer (min-height 100vh, body scrolls)
├── Sidebar (fixed, full viewport, overflow-y: auto on whole aside)
└── AppMain (margin-left for sidebar width)
    ├── Header (sticky)
    └── ContentArea (overflow-y: auto — ineffective height constraint)
```

### After

```
AppShell (100dvh, grid, overflow: hidden)
├── Sidebar (100dvh, flex column, overflow: hidden)
│   ├── SidebarBrand
│   ├── SidebarNavScroll (flex:1, min-height:0, overflow-y:auto)
│   └── SidebarFooter (flex-shrink:0, profile card)
└── ShellBody (100dvh, flex column, overflow: hidden)
    ├── Header (flex-shrink:0)
    └── MainScrollArea (flex:1, min-height:0, overflow-y:auto)
        └── ContentInner → children
```

Scroll container for app pages: **`MainScrollArea` only** (not `body`).

---

## 3. Files changed

| File | Change |
|------|--------|
| `resources/js/src/shared/components/layout/AppLayout/AppLayout.tsx` | Grid shell (`AppShell`), `ShellBody`, `MainScrollArea`; removed margin-offset main column |
| `resources/js/src/shared/components/layout/Sidebar/Sidebar.tsx` | Flex column sidebar, internal nav scroll, anchored profile footer |
| `resources/js/src/shared/components/layout/SidebarNavContext.tsx` | Desktop breakpoint `1024px`; exported `SIDEBAR_MOBILE_MAX_WIDTH` / `SIDEBAR_DESKTOP_MIN_WIDTH` |
| `resources/js/src/shared/components/layout/Header/Header.tsx` | Header as non-scrolling shell chrome (`flex-shrink: 0`) |
| `resources/js/src/styles/GlobalStyles.ts` | `html`, `body`, `#root` height `100%` for stable viewport shell |
| `resources/js/src/__tests__/unit/shared/AppLayout.test.tsx` | Layout structure and mobile drawer tests |
| `resources/js/src/__tests__/e2e/ui-screenshot-qa.spec.ts` | Added `/tickets` to screenshot matrix |
| `docs/SIDEBAR_LAYOUT_FIX_REPORT.md` | This report |

---

## 4. Desktop behavior (≥ 1024px)

- Sidebar occupies the first grid column at `--sidebar-width` (296px from theme).
- Sidebar stays full viewport height; does not move when main content scrolls.
- Long nav lists scroll inside `SidebarNavScroll`; profile card stays in `SidebarFooter`.
- Header remains visible; only `MainScrollArea` scrolls.
- No horizontal overflow from grid (`minmax(0, 1fr)` on main column).

---

## 5. Mobile / tablet behavior (< 1024px)

- Grid collapses to a single main column; sidebar becomes a **fixed drawer** (300px).
- Drawer hidden off-screen when collapsed; overlay (`sidebar-overlay`) closes on click.
- Header menu button toggles drawer via existing `SidebarNavContext`.
- Main content still scrolls inside `MainScrollArea`.

---

## 6. Scroll container decision

| Region | Scrolls? |
|--------|----------|
| `body` / `html` | No (for authenticated shell; login pages unchanged) |
| `AppShell` | No (`overflow: hidden`) |
| `Sidebar` root | No |
| `SidebarNavScroll` | Yes, when nav exceeds viewport |
| `MainScrollArea` | Yes, primary page scroll |

`min-height: 0` is applied on flex children (`SidebarNavScroll`, `MainScrollArea`) so overflow works inside the grid/flex shell.

---

## 7. Tests / build result

**Results (May 31, 2026):**

| Command | Result |
|---------|--------|
| `npm run type-check` | Pass |
| `npm run lint` | Pass |
| `npm run build` | Pass |
| `npm run test -- --run` | Pass (116 tests, including 3 new AppLayout tests) |

Screenshot QA (`npm run test:screenshots`) not run in this pass; `/tickets` was added to the e2e matrix for a future run.

Unit coverage added: `resources/js/src/__tests__/unit/shared/AppLayout.test.tsx`

- Renders shell, sidebar, and page content
- Asserts footer is outside nav scroll container
- Asserts mobile overlay presence when `useMediaQuery` reports mobile

---

## 8. Remaining layout risks

- **Very small viewport height:** Extremely short windows may still require nav scroll; footer remains visible but profile text may ellipsize.
- **Auth / marketing pages:** Still use `min-height: 100vh` on their own roots; unaffected by app shell.
- **Modals / portaled UI:** Continue to manage `body.overflow`; rare interaction if a modal opens while shell is mounted — existing Modal behavior unchanged.
- **RTL:** Grid and `inset-inline-start` drawer offsets should work; worth manual QA in Arabic locale.
- **Ticket detail right rail:** Page-internal layouts (e.g. ticket detail grid) are independent of app shell; verify on `/tickets/:id` after deploy.

---

## Regression checklist

Manual verification recommended on:

- `/dashboard`, `/tickets`, `/tickets/:id`, `/search`
- `/admin/users`, `/admin/statuses-priorities` (or ticket-meta route)
- `/settings`, `/notifications`

Confirm: stable sidebar, main-only scroll, visible footer, no double scrollbar, no horizontal overflow.
