diff --git a/.planning/phases/18-app-shell-navigation/18-RESEARCH.md b/.planning/phases/18-app-shell-navigation/18-RESEARCH.md new file mode 100644 index 0000000..ee8254d --- /dev/null +++ b/.planning/phases/18-app-shell-navigation/18-RESEARCH.md @@ -0,0 +1,547 @@ +# Phase 18: App Shell & Navigation - Research + +**Researched:** 2026-05-17 +**Domain:** Go/templ authenticated shell — sidebar rebuild + top header bar +**Confidence:** HIGH + +--- + + +## 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) + + + +## 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 | + + +--- + +## 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 `` 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.