docs(18): research phase domain
This commit is contained in:
parent
4b8149028d
commit
bc2cde8fa3
1 changed files with 547 additions and 0 deletions
547
.planning/phases/18-app-shell-navigation/18-RESEARCH.md
Normal file
547
.planning/phases/18-app-shell-navigation/18-RESEARCH.md
Normal 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.
|
||||
Loading…
Reference in a new issue