docs(18): research phase domain

This commit is contained in:
Arthur Belleville 2026-05-17 15:10:00 +02:00
parent 4b8149028d
commit bc2cde8fa3
No known key found for this signature in database

View file

@ -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>
## 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.