# Phase 18: App Shell & Navigation - Research

**Researched:** 2026-05-17
**Domain:** Go/templ authenticated shell — sidebar rebuild + top header bar
**Confidence:** HIGH

---

<user_constraints>
## User Constraints (from CONTEXT.md)

### Locked Decisions
- **D-01:** Figma file: `https://www.figma.com/design/6z32tx1YhL3nJHlX6M4IA2/XTABLO-Design--bbellev---copie-?node-id=11013-36540`
- **D-02:** Screenshots in `screenshots/` at repo root (Homepage.png, Board.png). Planner/executor MUST read screenshots before making visual decisions.
- **D-03:** Top header bar — three zones: Left = breadcrumb, Center = search placeholder, Right = bell/inbox/avatar
- **D-04:** `AppLayout` gets `pageTitle string` and `[]BreadcrumbItem{label, href}` params. Every handler call site sets its own breadcrumb.
- **D-05:** `AppLayout` gets `headerActions templ.Component` slot. Phase 18 passes nil.
- **D-06:** Avatar dropdown: workspace name + member count, members list, settings link `/settings`, logout button (red text). No theme toggle.
- **D-07:** Full HTML/CSS rebuild to Figma spec. Phase 15 structure is replaced, not patched.
- **D-08:** Nav sections: GENERAL label (Home, My Tasks, Projects, Events, Team Members) + PROJECTS label (tablo list). Chat and Files removed from primary nav.
- **D-09:** Sidebar collapse via JS toggling `is-collapsed` class. No server round-trip. No persistence.

### Claude's Discretion
- Avatar dropdown implementation mechanism (no Alpine.js).
- Exact CSS class naming for the new header bar components (follow `.dashboard-*` / `.sidebar-*` convention).
- Whether `BreadcrumbItem` is a struct or a simple string slice.

### Deferred Ideas (OUT OF SCOPE)
- Sidebar collapse persistence via cookie (v5.0 RESP-01)
- Sidebar responsive collapse on mobile (v5.0 RESP-01)
- Search functionality (future phase)
- Notifications / Inbox wiring (future phase)
- Settings page full implementation (future phase)
</user_constraints>

<phase_requirements>
## Phase Requirements

| ID | Description | Research Support |
|----|-------------|------------------|
| NAV-01 | User sees a redesigned sidebar/nav bar matching Figma (brand section, icon nav items, tablo list section, user footer) | Sidebar CSS token inventory, call site audit, `sidebarPrimaryNavItems` restructuring |
| NAV-02 | User sees a per-page top header bar with page title and contextual action buttons matching Figma | `AppLayout` signature extension, new `.page-header` CSS block, `BreadcrumbItem` struct design |
</phase_requirements>

---

## Summary

Phase 18 rebuilds the authenticated app shell from a single-column sidebar layout into a two-part shell: a redesigned sidebar (new section labels, updated nav items, collapse toggle) and a new per-page top header bar (breadcrumb, search placeholder, right-side icons, avatar dropdown).

The scope is narrow but affects every authenticated page simultaneously because `AppLayout` in `backend/templates/app_layout.templ` is the single render root for all authenticated routes. There are **5 direct `AppLayout` call sites** across 4 templ files, plus the `TabloDetailPage` template which wraps `AppLayout` and is called by 5 additional handlers. The signature change propagates mechanically — every call site must add `pageTitle` and `breadcrumb` arguments.

The CSS source of truth is `backend/internal/web/ui/app.css`. It is imported into `tailwind.css` via `backend/tailwind.input.css`. New header bar CSS goes into `app.css`, keeping the same section-comment pattern already established there. The compiled `tailwind.css` is regenerated from `tailwind.input.css` — never edited by hand.

**Primary recommendation:** Implement Phase 18 in 3 plans: (1) `AppLayout` signature + `BreadcrumbItem` struct + all call sites updated, (2) sidebar HTML/CSS full rebuild to Figma, (3) top header bar HTML/CSS + avatar dropdown + collapse JS.

---

## Screenshot Analysis

### Homepage.png (read as image)

**Sidebar (left column, ~220px wide):**
- Top: XTablo logo (circular/rounded icon) + "XTablo" text, centered vertically
- Section label "GENERAL" in small caps, muted, above the nav list
- Nav items with icons + labels: Home (active, highlighted), My Tasks, Projects, Events, Team Members
- Section label "PROJECTS" above a list of tablo entries (colored dot + name)
- Each tablo: small colored circle on left, project name, "..." overflow button on right
- Bottom: workspace entry "Creative Cloud" with a small avatar icon and "..." overflow
- Sidebar background is a light elevated surface; active item has a soft blue/brand fill

**Top header bar (spans full content area width):**
- Left zone: Page title "Good Afternoon, Jhon!" with date subtitle "Tuesday, January 5"
- Center zone: Search input with magnifier icon and placeholder text
- Right zone: Bell icon, "Inbox" label with count badge, avatar circle

**Content area:** Project grid + tasks list. The header bar sits above this content with a thin separator or padding.

### Board.png (read as image)

**Breadcrumb pattern visible:**
- Header bar left zone shows: "Dashboard > Project Details" — two levels, separated by `>`
- Center: search input (same as homepage)
- Right: same bell/inbox/avatar cluster

**Sidebar state:** Same structure, "Digital Marketing..." tablo is highlighted in the PROJECTS section.

**Key visual observations:**
- The header bar is visually flush with the top of the main content column, NOT a separate stripe above everything. It appears as padding/section inside `dashboard-main`.
- The avatar dropdown is not visible open in the screenshots — must be inferred from D-06 spec.
- The sidebar `is-collapsed` state is not shown — only the expanded state.

---

## Architectural Responsibility Map

| Capability | Primary Tier | Secondary Tier | Rationale |
|------------|-------------|----------------|-----------|
| Sidebar HTML/CSS structure | Frontend Server (SSR/templ) | — | Rendered server-side by `DashboardSidebar` component |
| Header bar HTML/CSS structure | Frontend Server (SSR/templ) | — | New templ component inside `AppLayout` |
| BreadcrumbItem data | Frontend Server (SSR/templ) | API (handler) | Struct lives in templates package; handlers supply values |
| Avatar dropdown interaction | Browser / Client | — | Needs JS toggle; no server round-trip required |
| Sidebar collapse toggle | Browser / Client | — | D-09: JS toggles `is-collapsed`, no persistence |
| Logout action | API / Backend | Browser | Existing POST `/logout` stays unchanged |
| CSS tokens | Frontend Server (SSR/templ) | — | `app.css` → `tailwind.input.css` → compiled `tailwind.css` |

---

## Current Code Inventory

### AppLayout Signature (current, Phase 15)

```go
// backend/templates/app_layout.templ line 153
templ AppLayout(title string, user *auth.User, csrfToken string, activePath string, tablos []sqlc.Tablo)
```

Parameters:
1. `title string` — HTML `<title>` content
2. `user *auth.User` — provides `Email` for avatar initial
3. `csrfToken string` — passed to logout form via `ui.CSRFField`
4. `activePath string` — drives `sidebarNavItemClass` active state
5. `tablos []sqlc.Tablo` — list of tablos for `SidebarProjectsSection`

**New signature after Phase 18:**
```go
templ AppLayout(
    title string,
    user *auth.User,
    csrfToken string,
    activePath string,
    tablos []sqlc.Tablo,
    pageTitle string,
    breadcrumb []BreadcrumbItem,
    headerActions templ.Component,
)
```

### Direct AppLayout Call Sites

[VERIFIED: grep of backend/templates/ *.templ]

| File | Template function | Call count |
|------|------------------|------------|
| `backend/templates/tablos.templ` | `TablosDashboard` | 1 (line 13) |
| `backend/templates/tablos.templ` | `TabloDetailPage` | 2 (lines 242, 648) |
| `backend/templates/planning.templ` | `PlanningPage` | 1 (line 9) |
| `backend/templates/account_providers.templ` | `AccountProvidersPage` | 1 (line 9) |

**Total direct `@AppLayout(...)` calls: 5** across 3 files.

`TabloDetailPage` is itself called by handlers — its signature also needs `pageTitle`/`breadcrumb` threaded through from each handler invocation:
- `handlers_tablos.go` calls `TabloDetailPage` at lines 225, 338
- `handlers_discussion.go` at line 78
- `handlers_files.go` at lines 106, 138
- `handlers_events.go` at line 177

**Total handler call sites that indirectly need updating: 5 handler call sites** (via `TabloDetailPage` signature).

### Existing sidebarPrimaryNavItems (Phase 15)

```go
// backend/templates/app_layout_helpers.go line 47
{Href: "/",        Label: "Dashboard", Icon: "panels",   Active: ..., DividerAfter: true},
{Href: "",         Label: "Tasks",     Icon: "tasks",    Active: false},
{Href: "/planning",Label: "Planning",  Icon: "planning", Active: ...},
{Href: "",         Label: "Chat",      Icon: "chat",     Active: false},
{Href: "",         Label: "Files",     Icon: "files",    Active: false},
```

**Phase 18 replacement per D-08:**
- GENERAL section label
- Home (`/`, icon: panels)
- My Tasks (`/tasks`, no route yet — non-interactive or `href="#"`)
- Projects (`/tablos`)
- Events (`/planning`, existing route)
- Team Members (no route — non-interactive)
- PROJECTS section label (existing `SidebarProjectsSection` renamed/relabeled)
- Chat and Files: removed from primary nav entirely

### Existing CSS Token Names (sidebar/shell block)

[VERIFIED: read of backend/internal/web/ui/app.css]

Shell and sidebar classes currently defined:
- `.dashboard-shell` — grid container (`grid-template-columns: minmax(16rem, 18rem) 1fr`)
- `.dashboard-sidebar` — safe-area left padding
- `.sidebar-nav-shell` — sticky column, flex column
- `.sidebar-brand`, `.sidebar-brand-link`, `.sidebar-brand-logo`, `.sidebar-brand-title`
- `.sidebar-collapse-button` — exists in CSS, currently unwired in HTML
- `.sidebar-primary`, `.sidebar-list`, `.sidebar-divider`
- `.sidebar-nav-item`, `.sidebar-nav-item.is-active`, `.sidebar-nav-link`, `.sidebar-nav-link-inner`
- `.sidebar-nav-icon`, `.sidebar-nav-label`
- `.sidebar-projects`, `.sidebar-section-label`, `.sidebar-project-list`
- `.sidebar-project-link`, `.sidebar-project-icon`, `.sidebar-project-label`
- `.sidebar-footer-links`, `.sidebar-organization`
- `.organization-button`, `.organization-avatar`, `.organization-copy`, `.organization-name`, `.organization-meta`
- `.dashboard-main` — flex column, `padding: 2rem`

Design tokens in use: `var(--color-surface-elevated)`, `var(--color-border-panel)`, `var(--shadow-sidebar)`, `var(--color-text-muted)`, `var(--color-text-brand)`, `var(--overlay-brand-soft-strong)`, `var(--overlay-dark-soft)`, `var(--color-surface-muted-inverse)`, `var(--color-border-panel-muted)`, `var(--shadow-floating-control)`, `var(--color-surface-elevated-strong)`.

**New classes needed for Phase 18 (header bar, to be added to app.css):**
- `.page-header` — the top bar container (flex row, 3-zone layout)
- `.page-header-left` — breadcrumb zone
- `.page-header-center` — search placeholder zone
- `.page-header-right` — bell/inbox/avatar cluster
- `.breadcrumb`, `.breadcrumb-item`, `.breadcrumb-separator`
- `.header-avatar-button` — clickable avatar in header
- `.header-avatar-dropdown`, `.header-avatar-dropdown.is-open` — dropdown panel
- `.header-search-placeholder` — styled but non-functional input shell
- `.is-collapsed` modifier on `.dashboard-sidebar` (for collapse toggle)
- Collapsed state rules: `.dashboard-shell.sidebar-is-collapsed` adjusts `grid-template-columns`

### CSS File Target

[VERIFIED: read of backend/tailwind.input.css]

The build pipeline is:
```
tailwind.input.css
  @import "tailwindcss"
  @import "./internal/web/ui/app.css"   ← sidebar/shell CSS lives here
  → compiled to → backend/static/tailwind.css
```

The existing sidebar block lives in `backend/internal/web/ui/app.css` (confirmed by file header comment "app.css — dashboard shell, sidebar, and project-card CSS"). All new Phase 18 CSS (header bar, breadcrumb, avatar dropdown, collapse state) belongs in `backend/internal/web/ui/app.css`, not in `tailwind.css` directly. `tailwind.css` is a compiled artifact.

### auth.User Struct

[VERIFIED: read of backend/internal/auth/types.go]

```go
type User struct {
    ID           uuid.UUID
    Email        string
    PasswordHash pgtype.Text
    CreatedAt    time.Time
    UpdatedAt    time.Time
}
```

Only `Email` is available for avatar display. There is no `Name`, `DisplayName`, or `AvatarURL` field. The initial letter from `Email` (already used in `SidebarOrganizationFooter`) is the only avatar content possible. The avatar dropdown workspace name must come from the email or a placeholder — there is no `Workspace` or `Organization` entity in the current `User` struct.

---

## Architecture Patterns

### System Architecture Diagram

```
HTTP request
     |
     v
chi router (authenticated middleware)
     |
     v
Handler (e.g., TablosDashboard)
  - resolves user, csrfToken, activePath, tablos
  - resolves pageTitle, breadcrumb []BreadcrumbItem     ← NEW
     |
     v
templ template call:
  TablosDashboard(user, csrfToken, activePath, tablos,
                  "Dashboard", []BreadcrumbItem{{...}}, nil)
     |
     v
AppLayout(title, user, csrfToken, activePath, tablos,
          pageTitle, breadcrumb, headerActions)        ← NEW PARAMS
     |
     +---> DashboardSidebar (rebuilt, D-07)
     |       +---> SidebarBrandSection
     |       +---> SidebarNavSection ("GENERAL" label + items)
     |       +---> SidebarProjectsSection ("PROJECTS" label + tablos)
     |       +---> collapse <button> (wires is-collapsed JS toggle)
     |
     +---> <div class="dashboard-main">
             +---> PageHeader(pageTitle, breadcrumb, headerActions, user)  ← NEW
             |       +---> BreadcrumbBar (left zone)
             |       +---> SearchPlaceholder (center zone)
             |       +---> HeaderRight: bell, inbox, AvatarDropdown (right zone)
             |               +---> <details>/<summary> or JS-toggled div
             |
             +---> { children... }  (page content, unchanged)
```

### Recommended Project Structure

No new directories needed. All changes go into existing files:

```
backend/
├── templates/
│   ├── app_layout.templ          # PRIMARY CHANGE: new params, rebuilt sidebar, new PageHeader component
│   └── app_layout_helpers.go     # Updated sidebarPrimaryNavItems, new BreadcrumbItem type
├── internal/web/ui/
│   └── app.css                   # New .page-header, .breadcrumb, .header-avatar-dropdown, collapse CSS
└── internal/web/
    ├── handlers_tablos.go        # All TabloDetailPage/TablosDashboard calls get new args
    ├── handlers_planning.go      # PlanningPage call gets new args
    ├── handlers_account.go       # AccountProvidersPage call gets new args
    ├── handlers_discussion.go    # TabloDetailPage call gets new args
    ├── handlers_files.go         # TabloDetailPage calls get new args
    └── handlers_events.go        # TabloDetailPage call gets new args
```

---

## Avatar Dropdown Implementation Recommendation

**Recommendation: `<details>/<summary>` element.**

Rationale:
- Native HTML, zero JavaScript, zero dependencies.
- Works without HTMX wiring. Clicking `<summary>` toggles the `<details open>` attribute natively.
- Dismisses on focus-out via `click` on backdrop or another element (use CSS `z-index` + positioning).
- Already sufficient for Phase 18 scope (no animation required per screenshots).
- Alpine.js is forbidden per constraints. HTMX `hx-on::click` + hidden-div toggle is workable but adds JS complexity for something HTML handles natively.
- The one limitation: `<details>` doesn't auto-close on outside click by default. A 2-line inline JS snippet (`document.addEventListener('click', ...)`) can handle this if needed — much smaller than a full toggle pattern. Phase 18 can ship with native `<details>` behavior first (click outside opens a new page click anyway).

**Implementation pattern:**
```html
<details class="header-avatar-menu">
  <summary class="header-avatar-button" aria-label="User menu">
    <span class="header-avatar-initial">{ initial }</span>
  </summary>
  <div class="header-avatar-dropdown">
    <!-- workspace name + member count -->
    <!-- members list -->
    <hr/>
    <a href="/settings">Settings</a>
    <form method="POST" action="/logout">
      @ui.CSRFField(csrfToken)
      <button type="submit" class="logout-button">Log out</button>
    </form>
  </div>
</details>
```

Close-on-outside-click (optional, ~5 lines inline JS):
```js
document.addEventListener('click', function(e) {
  document.querySelectorAll('details.header-avatar-menu').forEach(function(d) {
    if (!d.contains(e.target)) d.removeAttribute('open');
  });
});
```

---

## BreadcrumbItem Design Recommendation

**Recommendation: struct in `app_layout_helpers.go`.**

```go
// BreadcrumbItem represents one crumb in the top header breadcrumb trail.
// If Href is empty, the item renders as plain text (current page — no link).
type BreadcrumbItem struct {
    Label string
    Href  string
}
```

Rationale vs string slice:
- A `[]string` would lose the distinction between linked and non-linked crumbs (the final crumb is typically non-interactive).
- A `[]BreadcrumbItem` keeps call sites readable: `[]BreadcrumbItem{{"Dashboard", "/"}, {"Project Details", ""}}`.
- Consistent with the existing `sidebarNavItem` struct pattern in the same file.
- Zero additional Go imports required (no external dependency).

Typical call site:
```go
// handlers_tablos.go — dashboard page
breadcrumb := []templates.BreadcrumbItem{{"Dashboard", ""}}

// handlers_tablos.go — tablo detail page
breadcrumb := []templates.BreadcrumbItem{
    {"Dashboard", "/"},
    {tablo.Title, ""},
}
```

---

## Don't Hand-Roll

| Problem | Don't Build | Use Instead | Why |
|---------|-------------|-------------|-----|
| Avatar dropdown | Custom Alpine.js component | `<details>/<summary>` | Native HTML, zero JS required |
| CSRF token in logout form | Custom form builder | `ui.CSRFField(csrfToken)` | Already exists, tested |
| Icon rendering | New SVG inline | `SidebarNavIcon(kind string)` | All 6 icons already defined |
| Component slots | Custom rendering interface | `templ.Component` (already used in `ui.Card`, `ui.EmptyState`, `ui.Table`) | Established pattern |

---

## Common Pitfalls

### Pitfall 1: Editing tailwind.css directly
**What goes wrong:** CSS changes get overwritten on next `pnpm build` / tailwind compilation.
**Why it happens:** `tailwind.css` is a build artifact of `tailwind.input.css` + `app.css`.
**How to avoid:** All new CSS goes into `backend/internal/web/ui/app.css`. Run `pnpm run build:css` (or the equivalent) to regenerate.
**Warning signs:** Changes present in app.css but missing from tailwind.css after a build means the import is broken; changes directly in tailwind.css but not in app.css will disappear.

### Pitfall 2: Missing call site update (signature mismatch)
**What goes wrong:** Go compiler error — templ generates Go code and `AppLayout` call sites with wrong arity will fail to compile.
**Why it happens:** 5 direct `@AppLayout(...)` calls + `TabloDetailPage` signature which itself is called by 5 handlers.
**How to avoid:** Change `AppLayout` signature, then compile. The compiler will report every broken call site. Do not rely on grep alone — compile to confirm all sites updated.
**Warning signs:** `too many arguments in call to AppLayout` or `not enough arguments` compile errors.

### Pitfall 3: `is-collapsed` CSS grid not adjusting sidebar width
**What goes wrong:** Sidebar icons overlap content area when collapsed because `grid-template-columns` on `.dashboard-shell` is fixed at `minmax(16rem, 18rem) 1fr`.
**Why it happens:** CSS toggle via `is-collapsed` class only hides labels; the grid column does not shrink unless explicitly targeted.
**How to avoid:** Add a `.dashboard-shell.sidebar-is-collapsed` rule that changes `grid-template-columns` to a narrow icon-only width (e.g., `4rem 1fr`). The JS toggle must apply `is-collapsed` to the shell container, not just the sidebar element.
**Warning signs:** Labels disappear but a white gap remains where the full-width sidebar was.

### Pitfall 4: Logout form moved — test still checks old location
**What goes wrong:** `TestTablosDashboard_Sidebar` currently checks for `dashboard-sidebar` and `sidebar-nav-shell`. If the test is not updated to also verify the new header bar or the logout button's new location, it passes even with a broken UI.
**Why it happens:** Test was written for Phase 15 structure; it checks class names that will still exist after the rebuild.
**How to avoid:** Update the sidebar test to check for new Phase 18 class names (`page-header`, `breadcrumb`, `header-avatar-button`). Add a test that verifies the logout button renders in the header dropdown, not in the sidebar.

### Pitfall 5: Avatar dropdown missing CSRF token
**What goes wrong:** Logout POST from the avatar dropdown is rejected with CSRF error.
**Why it happens:** The logout form moves from `SidebarOrganizationFooter` to the header dropdown; `csrfToken` must be threaded into the new component.
**How to avoid:** The `csrfToken` is already an `AppLayout` parameter — pass it to `PageHeader` / `AvatarDropdown` component. Use `ui.CSRFField(csrfToken)` in the logout form.

### Pitfall 6: `templ.Component` nil check for headerActions
**What goes wrong:** Rendering a nil `templ.Component` panics if the template calls `@headerActions` unconditionally.
**Why it happens:** Phase 18 passes `nil` for `headerActions`; templ does not nil-guard automatically.
**How to avoid:** Guard the render with `if headerActions != nil { @headerActions }`.

---

## Existing Test Coverage

[VERIFIED: read of handlers_tablos_test.go]

Relevant existing test: `TestTablosDashboard_Sidebar` (line 602) — checks for `"dashboard-sidebar"` and `"sidebar-nav-shell"` in the response body.

After Phase 18:
- These class names are preserved (the sidebar element still uses `.dashboard-sidebar` and `.sidebar-nav-shell`), so the existing test continues to pass.
- New assertions needed: `page-header`, `breadcrumb`, `header-avatar-button` or `header-avatar-menu`.

---

## Recommended Plan Decomposition

Phase 18 should be implemented in **3 plans**:

### Plan 1: Signature extension and call site migration
**Scope:** All the Go/templ plumbing before any visual change.
- Define `BreadcrumbItem` struct in `app_layout_helpers.go`
- Extend `AppLayout` signature with `pageTitle string`, `breadcrumb []BreadcrumbItem`, `headerActions templ.Component`
- Update all 5 direct `@AppLayout(...)` call sites in templ files
- Update `TabloDetailPage` templ signature to accept and pass through the new params
- Update all 5 handler call sites that call `TabloDetailPage`
- Update handlers that call `TablosDashboard`, `PlanningPage`, `AccountProvidersPage` directly
- Temporary: render `pageTitle` and `breadcrumb` as plain text in `AppLayout` (no styling yet) — ensures compile passes
- Goal: codebase compiles and all handlers pass correct breadcrumbs

**Deliverable:** Green compile, `go test ./...` passes, all pages still render (structurally).

### Plan 2: Sidebar HTML/CSS full rebuild
**Scope:** Replace `DashboardSidebar` internals and CSS to match Figma.
- Rewrite `DashboardSidebar` in `app_layout.templ` with new section structure (brand, GENERAL label, nav items, PROJECTS label, tablo list)
- Update `sidebarPrimaryNavItems` in `app_layout_helpers.go` per D-08 (new items, new icons where needed, Chat/Files removed)
- Add `<button class="sidebar-collapse-button">` wired to inline JS that toggles `is-collapsed` on `.dashboard-shell`
- Update `backend/internal/web/ui/app.css`: add collapsed-state CSS, update any section class names that changed
- Remove `SidebarOrganizationFooter` (logout form moves to header in Plan 3; temporarily keep it or omit if Plan 3 is next)
- Regenerate `tailwind.css` from `tailwind.input.css`

**Deliverable:** Sidebar visually matches Figma screenshots.

### Plan 3: Top header bar + avatar dropdown
**Scope:** New `PageHeader` templ component and all supporting CSS.
- Implement `PageHeader(pageTitle string, breadcrumb []BreadcrumbItem, headerActions templ.Component, user *auth.User, csrfToken string)` in `app_layout.templ`
- Three zones: breadcrumb (left), search placeholder (center), icons + avatar (right)
- Avatar dropdown using `<details>/<summary>` with workspace name, logout form (CSRF), settings link
- Add `.page-header`, `.breadcrumb`, `.header-avatar-menu`, `.header-avatar-dropdown`, `.header-search-placeholder`, `.header-icon-button` CSS to `app.css`
- Wire `PageHeader` into `AppLayout` above `{ children... }`
- Update `dashboard-main` padding if header bar sits inside it
- Update `TestTablosDashboard_Sidebar` to assert `page-header` and `breadcrumb` class names
- Regenerate `tailwind.css`

**Deliverable:** Every authenticated page shows top header bar matching Figma; avatar dropdown works; logout still functions.

---

## Validation Architecture

### Test Framework
| Property | Value |
|----------|-------|
| Framework | Go `testing` + `net/http/httptest` |
| Config file | none (standard Go test runner) |
| Quick run command | `cd backend && go test ./internal/web/ -run TestTablosDashboard -v` |
| Full suite command | `cd backend && go test ./...` |

### Phase Requirements → Test Map
| Req ID | Behavior | Test Type | Automated Command | File Exists? |
|--------|----------|-----------|-------------------|-------------|
| NAV-01 | Sidebar renders with `sidebar-nav-shell`, GENERAL/PROJECTS section labels, collapse button | integration | `go test ./internal/web/ -run TestTablosDashboard_Sidebar -v` | Partially — existing test needs new assertions |
| NAV-02 | Every authenticated page renders `.page-header` with breadcrumb | integration | `go test ./internal/web/ -run TestTablosDashboard_Header -v` | No — Wave 0 gap |

### Wave 0 Gaps
- [ ] `backend/internal/web/handlers_tablos_test.go` — update `TestTablosDashboard_Sidebar` to assert new class names (`page-header`, `breadcrumb`, sidebar GENERAL/PROJECTS labels)
- [ ] New test `TestTablosDashboard_Header` — asserts `.page-header` and breadcrumb text in response body (Wave 0 of Plan 3)

---

## Open Questions

1. **Workspace name in avatar dropdown (D-06)**
   - What we know: `auth.User` has only `Email` — no workspace/org name field.
   - What's unclear: D-06 specifies "workspace name + member count" but there is no workspace entity in v4.0.
   - Recommendation: Use the email as the workspace name placeholder in Phase 18 (same pattern as the current `SidebarOrganizationFooter`). The member list can show only the current user. Document this as a v5.0 placeholder.

2. **`/tablos` route vs `/` route for "Projects" nav item**
   - What we know: D-08 says "Projects (`/tablos`)". Current `TablosDashboard` is at `/` (root).
   - What's unclear: Does the router have a GET `/tablos` route, or does the Projects nav item go to `/`?
   - Recommendation: Check `router.go` before Plan 2. If `/tablos` is not registered, either register it as an alias for the tablos dashboard or point the nav item to `/` and label it "Home" as in Figma Homepage.png.

---

## Sources

### Primary (HIGH confidence)
- `backend/templates/app_layout.templ` — read directly; full component inventory
- `backend/templates/app_layout_helpers.go` — read directly; `sidebarNavItem`, `sidebarPrimaryNavItems`
- `backend/internal/web/ui/app.css` — read directly; full CSS class inventory
- `backend/tailwind.input.css` — read directly; CSS build pipeline confirmed
- `backend/internal/auth/types.go` — read directly; `auth.User` struct fields confirmed
- `screenshots/Homepage.png`, `screenshots/Board.png` — read as images; visual design confirmed

### Secondary (MEDIUM confidence)
- `backend/internal/web/handlers_*.go` — grepped for call sites; 5 direct + 5 indirect confirmed
- `backend/internal/web/handlers_tablos_test.go` — read directly; existing test assertions confirmed
- `.planning/phases/18-app-shell-navigation/18-CONTEXT.md` — read directly; all locked decisions

---

## Metadata

**Confidence breakdown:**
- Standard stack: HIGH — all code read directly from source
- Architecture: HIGH — all call sites enumerated by grep + read
- Pitfalls: HIGH — derived from actual code structure, test file content, and CSS build pipeline

**Research date:** 2026-05-17
**Valid until:** This is a code-audit research — valid until the files change. Rerun grep audit if major refactoring occurs before planning completes.
