diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md index ccfcad4..abea957 100644 --- a/.planning/ROADMAP.md +++ b/.planning/ROADMAP.md @@ -8,7 +8,7 @@ ## v4.0 Active Phases -- [ ] Phase 18: App Shell & Navigation — sidebar redesign + top header bar (NAV-01, NAV-02) +- [x] Phase 18: App Shell & Navigation — sidebar redesign + top header bar (NAV-01, NAV-02) — completed 2026-05-17 - [ ] Phase 19: Tablo List Revamp — card redesign, progress bar, status field, list/card toggle (LIST-01, LIST-02, LIST-03) - [ ] Phase 20: Tablo Detail & Kanban — detail page + kanban restyled to Figma (DETAIL-01, TASK-01) - [ ] Phase 21: Task Grid & Roadmap Views — new grid view + new roadmap view with date fields (TASK-02, TASK-03) @@ -56,11 +56,17 @@ ### Phase 18: App Shell & Navigation **Goal:** Redesign the sidebar and top header bar to match the Figma design. **Requirements:** NAV-01, NAV-02 +**Plans:** 3 plans **Success criteria:** 1. Sidebar renders brand section, icon nav items, tablo list section, and user footer matching Figma 2. Every authenticated page shows a top header bar with page title and contextual actions 3. Existing navigation functionality (logout, tablo selection) is preserved +Plans: +- [ ] 18-01-PLAN.md — AppLayout signature extension + BreadcrumbItem struct + all call sites updated +- [ ] 18-02-PLAN.md — Sidebar full HTML/CSS rebuild to Figma spec with collapse toggle +- [ ] 18-03-PLAN.md — PageHeader component, avatar dropdown, /settings stub, tests updated + ### Phase 19: Tablo List Revamp **Goal:** Restyle the tablos page with revamped cards, real progress data, list/card toggle, and status field. **Requirements:** LIST-01, LIST-02, LIST-03 @@ -118,7 +124,7 @@ | 15. Dashboard & Tablos | v3.0 | 3/3 | Complete | 2026-05-16 | | 16. Tablo Detail | v3.0 | 4/4 | Complete | 2026-05-17 | | 17. Chat & Planning | v3.0 | 2/2 | Complete | 2026-05-17 | -| 18. App Shell & Navigation | v4.0 | — | Pending | — | +| 18. App Shell & Navigation | v4.0 | 0/3 | Pending | — | | 19. Tablo List Revamp | v4.0 | — | Pending | — | | 20. Tablo Detail & Kanban | v4.0 | — | Pending | — | | 21. Task Grid & Roadmap Views | v4.0 | — | Pending | — | diff --git a/.planning/STATE.md b/.planning/STATE.md index 89cb333..7eed0c4 100644 --- a/.planning/STATE.md +++ b/.planning/STATE.md @@ -2,15 +2,14 @@ gsd_state_version: 1.0 milestone: v4.0 milestone_name: Figma Design Parity -status: planning -last_updated: "2026-05-17T12:22:07.603Z" -last_activity: 2026-05-17 +status: Executing +last_updated: "2026-05-17T16:05:00.000Z" +last_activity: 2026-05-17 — Phase 18 complete (sidebar + header restyle approved) progress: - total_phases: 0 - completed_phases: 0 - total_plans: 0 - completed_plans: 0 - percent: 0 + total_phases: 5 + completed_phases: 1 + total_plans: 3 + completed_plans: 3 --- # STATE @@ -28,10 +27,10 @@ See: `.planning/PROJECT.md` (updated 2026-05-17) ## Current Position -Phase: Not started (defining requirements) +Phase: 19 — Tablo List Revamp (next) Plan: — -Status: Defining requirements -Last activity: 2026-05-17 — Milestone v4.0 started +Status: Executing +Last activity: 2026-05-17 — Phase 18 complete ## Previous Milestone Status diff --git a/.planning/phases/18-app-shell-navigation/18-01-PLAN.md b/.planning/phases/18-app-shell-navigation/18-01-PLAN.md new file mode 100644 index 0000000..d79e23b --- /dev/null +++ b/.planning/phases/18-app-shell-navigation/18-01-PLAN.md @@ -0,0 +1,318 @@ +--- +phase: 18-app-shell-navigation +plan: 01 +type: execute +wave: 1 +depends_on: [] +files_modified: + - backend/templates/app_layout_helpers.go + - backend/templates/app_layout.templ + - backend/templates/tablos.templ + - backend/templates/planning.templ + - backend/templates/account_providers.templ + - backend/internal/web/handlers_tablos.go + - backend/internal/web/handlers_planning.go + - backend/internal/web/handlers_account.go + - backend/internal/web/handlers_discussion.go + - backend/internal/web/handlers_files.go + - backend/internal/web/handlers_events.go +autonomous: true +requirements: + - NAV-01 + - NAV-02 + +must_haves: + truths: + - "The Go codebase compiles with zero errors after AppLayout signature is extended" + - "Every authenticated page still renders (no 500 errors) — dashboard, tablo detail, planning, account providers" + - "go test ./... passes with no failures" + artifacts: + - path: "backend/templates/app_layout_helpers.go" + provides: "BreadcrumbItem struct export" + contains: "type BreadcrumbItem struct" + - path: "backend/templates/app_layout.templ" + provides: "Extended AppLayout signature with pageTitle, breadcrumb, headerActions" + contains: "pageTitle string" + - path: "backend/templates/tablos.templ" + provides: "TablosDashboard and TabloDetailPage updated with breadcrumb params" + key_links: + - from: "backend/internal/web/handlers_tablos.go" + to: "backend/templates/tablos.templ" + via: "TablosDashboard / TabloDetailPage call with breadcrumb args" + pattern: "BreadcrumbItem" + - from: "backend/templates/tablos.templ" + to: "backend/templates/app_layout.templ" + via: "AppLayout call with pageTitle and breadcrumb" + pattern: "AppLayout.*pageTitle" +--- + + +Extend AppLayout with three new parameters (pageTitle, breadcrumb, headerActions) and update every call site mechanically so the codebase compiles. No visual changes yet — the new params are threaded through but rendered minimally (pageTitle in the HTML title tag, breadcrumb as plain inline text). + +Purpose: Establishes the Go/templ contracts that Plans 02 and 03 build on. Plans 02 and 03 cannot safely start until this compiles clean. +Output: A green compile + passing test suite. All handlers supply correct per-page breadcrumbs. + + + +@/Users/arthur.belleville/Documents/perso/projects/xtablo-source/.claude/get-shit-done/workflows/execute-plan.md +@/Users/arthur.belleville/Documents/perso/projects/xtablo-source/.claude/get-shit-done/templates/summary.md + + + +@.planning/ROADMAP.md +@.planning/REQUIREMENTS.md +@.planning/phases/18-app-shell-navigation/18-CONTEXT.md +@.planning/phases/18-app-shell-navigation/18-RESEARCH.md + + + + + +templ AppLayout(title string, user *auth.User, csrfToken string, activePath string, tablos []sqlc.Tablo) + + + +templ TablosDashboard(user *auth.User, csrfToken string, activePath string, tablos []sqlc.Tablo, cards []TabloCardView) + + +templ TabloDetailPage(user *auth.User, csrfToken string, activePath string, sidebarTablos []sqlc.Tablo, tablo sqlc.Tablo, tasks []sqlc.Task, etapes []sqlc.Etape, counts EtapeTaskCounts, filter EtapeFilter, files []sqlc.TabloFile, events EventsCalendar, discussion DiscussionTabData, activeTab string) + + +templ PlanningPage(user *auth.User, csrfToken string, activePath string, tablos []sqlc.Tablo, agenda PlanningAgenda) + + +templ AccountProvidersPage(user *auth.User, providers []LinkedProviderStatus, csrfToken string, activePath string, tablos []sqlc.Tablo) + + +templates.TablosDashboard(user, csrf.Token(r), "/", sidebarTablos, cardViews) +templates.TabloDetailPage(user, csrf.Token(r), "", sidebarTablos, tablo, tasks, nil, templates.EtapeTaskCounts{}, templates.EtapeFilter{}, nil, templates.EventsCalendar{}, templates.DiscussionTabData{}, "overview") + + +templates.PlanningPage(user, csrf.Token(r), "/planning", sidebarTablos, agenda) +templates.AccountProvidersPage(user, statuses, csrf.Token(r), "/", sidebarTablos) +templates.TabloDetailPage(user, csrf.Token(r), "", discussionSidebarTablos, tablo, nil, nil, templates.EtapeTaskCounts{}, templates.EtapeFilter{}, nil, templates.EventsCalendar{}, data, "discussion") + + + + + + Task 1: Define BreadcrumbItem and extend AppLayout signature + backend/templates/app_layout_helpers.go, backend/templates/app_layout.templ + +In app_layout_helpers.go, add the BreadcrumbItem struct immediately after the sidebarNavItem type declaration (around line 6): + + // 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 + } + +In app_layout.templ, update the AppLayout signature from: + + templ AppLayout(title string, user *auth.User, csrfToken string, activePath string, tablos []sqlc.Tablo) + +to: + + templ AppLayout(title string, user *auth.User, csrfToken string, activePath string, tablos []sqlc.Tablo, pageTitle string, breadcrumb []BreadcrumbItem, headerActions templ.Component) + +The templ import for "github.com/a-h/templ" is already pulled in transitively via the ui package usage. If the compiler complains, add it explicitly to the import block at the top of app_layout.templ. + +Inside the AppLayout body, make a minimal placeholder so breadcrumb params are consumed (prevents unused-variable compile errors). Immediately before the DashboardSidebar call or inside the dashboard-main div, add a comment-only breadcrumb stub: + + if len(breadcrumb) > 0 { + + } + if headerActions != nil { + @headerActions + } + +The nil-guard on headerActions is required (per research Pitfall 6 — rendering nil templ.Component panics). + +Do NOT change any visual output yet. The goal is compiler compatibility only. + + + cd /Users/arthur.belleville/Documents/perso/projects/xtablo-source/backend && templ generate ./... 2>&1 | head -20 + + app_layout_helpers.go exports BreadcrumbItem struct. app_layout.templ AppLayout signature has 8 parameters. templ generate produces no errors. + + + + Task 2: Update all templ wrapper signatures and their internal AppLayout calls + backend/templates/tablos.templ, backend/templates/planning.templ, backend/templates/account_providers.templ + +Each templ function that calls AppLayout must (a) accept pageTitle and breadcrumb in its own signature, and (b) pass them through to AppLayout. Update each file as follows. + +--- tablos.templ --- + +TablosDashboard (line 12): Add two params at the end of its signature: + templ TablosDashboard(user *auth.User, csrfToken string, activePath string, tablos []sqlc.Tablo, cards []TabloCardView, pageTitle string, breadcrumb []BreadcrumbItem) +Update its AppLayout call (line 13) to: + @AppLayout("Tablos — Xtablo", user, csrfToken, activePath, tablos, pageTitle, breadcrumb, nil) + +TabloDetailPage (line 241): Add two params at the end: + templ TabloDetailPage(user *auth.User, csrfToken string, activePath string, sidebarTablos []sqlc.Tablo, tablo sqlc.Tablo, tasks []sqlc.Task, etapes []sqlc.Etape, counts EtapeTaskCounts, filter EtapeFilter, files []sqlc.TabloFile, events EventsCalendar, discussion DiscussionTabData, activeTab string, pageTitle string, breadcrumb []BreadcrumbItem) +Update its two AppLayout calls (lines ~242 and ~648) to pass pageTitle, breadcrumb, nil as the last three args. + +The TabloNotFoundPage function near line 648 also wraps AppLayout — update it too. It can pass hardcoded values: + @AppLayout("Not found", user, csrfToken, activePath, sidebarTablos, "Not found", nil, nil) + +--- planning.templ --- + +PlanningPage (line 8): Add two params at end: + templ PlanningPage(user *auth.User, csrfToken string, activePath string, tablos []sqlc.Tablo, agenda PlanningAgenda, pageTitle string, breadcrumb []BreadcrumbItem) +Update AppLayout call (line 9) to pass pageTitle, breadcrumb, nil. + +--- account_providers.templ --- + +AccountProvidersPage (line 8): Add two params at end: + templ AccountProvidersPage(user *auth.User, providers []LinkedProviderStatus, csrfToken string, activePath string, tablos []sqlc.Tablo, pageTitle string, breadcrumb []BreadcrumbItem) +Update AppLayout call (line 9) to pass pageTitle, breadcrumb, nil. + +Keep all other content in each file exactly as-is. + + + cd /Users/arthur.belleville/Documents/perso/projects/xtablo-source/backend && templ generate ./... 2>&1 | tail -5 + + All three templ files compile without errors. TablosDashboard, TabloDetailPage, PlanningPage, and AccountProvidersPage all accept and pass through pageTitle and breadcrumb. + + + + Task 3: Update all handler call sites with per-page breadcrumbs + backend/internal/web/handlers_tablos.go, backend/internal/web/handlers_planning.go, backend/internal/web/handlers_account.go, backend/internal/web/handlers_discussion.go, backend/internal/web/handlers_files.go, backend/internal/web/handlers_events.go + +Each handler that calls a templ function must now pass two additional arguments. Use the breadcrumb values below. The BreadcrumbItem type is referenced as templates.BreadcrumbItem in handler files. + +--- handlers_tablos.go --- + +TablosListHandler (line ~60, calls TablosDashboard): + templates.TablosDashboard(user, csrf.Token(r), "/", sidebarTablos, cardViews, + "Dashboard", + []templates.BreadcrumbItem{{Label: "Dashboard", Href: ""}}, + ) + +Error fallback call at line ~465 (same handler pattern): + templates.TablosDashboard(user, csrf.Token(r), "/", errorSidebarTablos, errorCardViews, + "Dashboard", + []templates.BreadcrumbItem{{Label: "Dashboard", Href: ""}}, + ) + +TabloDetailHandler (line ~225, calls TabloDetailPage): + templates.TabloDetailPage(user, csrf.Token(r), "", sidebarTablos, tablo, tasks, nil, + templates.EtapeTaskCounts{}, templates.EtapeFilter{}, nil, templates.EventsCalendar{}, + templates.DiscussionTabData{}, "overview", + tablo.Title, + []templates.BreadcrumbItem{{Label: "Dashboard", Href: "/"}, {Label: tablo.Title, Href: ""}}, + ) + +TabloUpdateHandler (line ~338, calls TabloDetailPage): + templates.TabloDetailPage(user, csrf.Token(r), "", updateSidebarTablos, tablo, tasks, nil, + templates.EtapeTaskCounts{}, templates.EtapeFilter{}, nil, templates.EventsCalendar{}, + templates.DiscussionTabData{}, "overview", + tablo.Title, + []templates.BreadcrumbItem{{Label: "Dashboard", Href: "/"}, {Label: tablo.Title, Href: ""}}, + ) + +--- handlers_planning.go --- + +PlanningPageHandler (line ~68, calls PlanningPage): + templates.PlanningPage(user, csrf.Token(r), "/planning", sidebarTablos, agenda, + "Planning", + []templates.BreadcrumbItem{{Label: "Planning", Href: ""}}, + ) + +--- handlers_account.go --- + +AccountProvidersHandler (line ~49, calls AccountProvidersPage): + templates.AccountProvidersPage(user, statuses, csrf.Token(r), "/", sidebarTablos, + "Linked Providers", + []templates.BreadcrumbItem{{Label: "Linked Providers", Href: ""}}, + ) + +--- handlers_discussion.go --- + +Discussion handler (line ~78, calls TabloDetailPage): + templates.TabloDetailPage(user, csrf.Token(r), "", discussionSidebarTablos, tablo, nil, nil, + templates.EtapeTaskCounts{}, templates.EtapeFilter{}, nil, templates.EventsCalendar{}, + data, "discussion", + tablo.Title, + []templates.BreadcrumbItem{{Label: "Dashboard", Href: "/"}, {Label: tablo.Title, Href: ""}}, + ) + +--- handlers_files.go --- + +Files tab handler (line ~106, calls TabloDetailPage): + templates.TabloDetailPage(user, csrf.Token(r), "", filesSidebarTablos, tablo, nil, nil, + templates.EtapeTaskCounts{}, templates.EtapeFilter{}, fileList, templates.EventsCalendar{}, + templates.DiscussionTabData{}, "files", + tablo.Title, + []templates.BreadcrumbItem{{Label: "Dashboard", Href: "/"}, {Label: tablo.Title, Href: ""}}, + ) + +Tasks-tab-from-files handler (line ~138, calls TabloDetailPage): + templates.TabloDetailPage(user, csrf.Token(r), "", tasksSidebarTablos, tablo, tasks, etapes, + counts, filter, nil, templates.EventsCalendar{}, templates.DiscussionTabData{}, "tasks", + tablo.Title, + []templates.BreadcrumbItem{{Label: "Dashboard", Href: "/"}, {Label: tablo.Title, Href: ""}}, + ) + +--- handlers_events.go --- + +Events tab handler (line ~177, calls TabloDetailPage): + templates.TabloDetailPage(user, csrf.Token(r), "", eventsSidebarTablos, tablo, nil, nil, + templates.EtapeTaskCounts{}, templates.EtapeFilter{}, nil, calendar, + templates.DiscussionTabData{}, "events", + tablo.Title, + []templates.BreadcrumbItem{{Label: "Dashboard", Href: "/"}, {Label: tablo.Title, Href: ""}}, + ) + +After all edits, run the full build to confirm zero compile errors: + cd backend && go build ./... + +The build MUST succeed before considering this task complete. If any call site is missed, the compiler reports "too many arguments" or "not enough arguments" — fix each reported site. + + + cd /Users/arthur.belleville/Documents/perso/projects/xtablo-source/backend && go build ./... && go test ./... 2>&1 | tail -20 + + go build ./... succeeds with no errors. go test ./... passes. Every authenticated page (dashboard, tablo detail, planning, account providers) renders without 500 errors when the server is started. + + + + + +## Trust Boundaries + +| Boundary | Description | +|----------|-------------| +| Handler → Template | Handlers supply breadcrumb strings — these originate from DB data (tablo.Title) which is user-controlled content rendered via templ's auto-escaping | + +## STRIDE Threat Register + +| Threat ID | Category | Component | Disposition | Mitigation Plan | +|-----------|----------|-----------|-------------|-----------------| +| T-18-01-01 | Tampering | tablo.Title in breadcrumb | accept | templ auto-escapes all string interpolations; no raw HTML insertion | +| T-18-01-02 | Denial of Service | templ generate fails mid-compile leaving partial _templ.go files | mitigate | Always run templ generate before go build; check templ output first | + + + +1. `cd backend && templ generate ./...` — zero errors +2. `cd backend && go build ./...` — zero compile errors +3. `cd backend && go test ./...` — all tests pass +4. Manual smoke: `cd backend && go run . serve` then visit `http://localhost:8080` — dashboard renders without 500 +5. Verify grep: `grep -r "BreadcrumbItem" backend/templates/app_layout_helpers.go` — shows struct definition +6. Verify grep: `grep -c "pageTitle" backend/templates/app_layout.templ` — returns 1 or more (param present) + + + +- BreadcrumbItem struct is exported from backend/templates package +- AppLayout has 8 parameters (was 5) +- TablosDashboard, TabloDetailPage, PlanningPage, AccountProvidersPage all accept and forward pageTitle + breadcrumb +- All 10 handler call sites pass the two new arguments +- go build ./... succeeds +- go test ./... passes +- No authenticated page returns a 500 + + + +After completion, create `.planning/phases/18-app-shell-navigation/18-01-SUMMARY.md` + diff --git a/.planning/phases/18-app-shell-navigation/18-02-PLAN.md b/.planning/phases/18-app-shell-navigation/18-02-PLAN.md new file mode 100644 index 0000000..25bd096 --- /dev/null +++ b/.planning/phases/18-app-shell-navigation/18-02-PLAN.md @@ -0,0 +1,326 @@ +--- +phase: 18-app-shell-navigation +plan: 02 +type: execute +wave: 2 +depends_on: + - 18-01 +files_modified: + - backend/templates/app_layout.templ + - backend/templates/app_layout_helpers.go + - backend/internal/web/ui/app.css +autonomous: true +requirements: + - NAV-01 + +must_haves: + truths: + - "Sidebar shows a GENERAL section label above Home, My Tasks, Projects, Events, Team Members nav items" + - "Sidebar shows a PROJECTS section label above the tablo list" + - "Chat and Files nav items are removed from the sidebar" + - "Clicking the collapse button toggles the sidebar between full-width and icon-only states" + - "Sidebar collapse does not trigger a server round-trip — state resets on page reload" + - "go build ./... succeeds after sidebar rebuild" + artifacts: + - path: "backend/templates/app_layout.templ" + provides: "Rebuilt DashboardSidebar with GENERAL + PROJECTS section labels and collapse button" + contains: "GENERAL" + - path: "backend/templates/app_layout_helpers.go" + provides: "Updated sidebarPrimaryNavItems with Figma-spec items" + contains: "My Tasks" + - path: "backend/internal/web/ui/app.css" + provides: "is-collapsed CSS rules, sidebar section label styles" + contains: "is-collapsed" + key_links: + - from: "backend/templates/app_layout.templ" + to: "backend/internal/web/ui/app.css" + via: "is-collapsed class toggled by inline JS, picked up by CSS rule" + pattern: "is-collapsed" + - from: "backend/templates/app_layout_helpers.go" + to: "backend/templates/app_layout.templ" + via: "sidebarPrimaryNavItems called in DashboardSidebar" + pattern: "sidebarPrimaryNavItems" +--- + + +Full HTML/CSS rebuild of the sidebar to match the Figma design (Homepage.png, Board.png). Replaces the Phase 15 DashboardSidebar internals with the new two-section structure (GENERAL + PROJECTS), wires the collapse toggle via inline JS, and updates app.css with collapsed-state rules. + +Purpose: Delivers NAV-01. After this plan the sidebar visually matches Figma. +Output: Rebuilt DashboardSidebar templ component + updated sidebarPrimaryNavItems + new CSS rules in app.css. + + + +@/Users/arthur.belleville/Documents/perso/projects/xtablo-source/.claude/get-shit-done/workflows/execute-plan.md +@/Users/arthur.belleville/Documents/perso/projects/xtablo-source/.claude/get-shit-done/templates/summary.md + + + +@.planning/ROADMAP.md +@.planning/phases/18-app-shell-navigation/18-CONTEXT.md +@.planning/phases/18-app-shell-navigation/18-RESEARCH.md +@.planning/phases/18-app-shell-navigation/18-01-SUMMARY.md + + + + + + + +templ DashboardSidebar(activePath string, tablos []sqlc.Tablo, user *auth.User, csrfToken string) + + +.dashboard-sidebar — outer aside element +.sidebar-nav-shell — inner nav element +.sidebar-brand — brand/logo section +.sidebar-primary — nav list container +.sidebar-nav-item — individual nav item +.sidebar-nav-item.is-active +.sidebar-projects — projects section container +.sidebar-section-label — section label text (GENERAL, PROJECTS) + + +.dashboard-shell — grid container (currently: minmax(16rem, 18rem) 1fr) +.dashboard-shell.sidebar-is-collapsed — new rule: grid-template-columns: 4rem 1fr +.sidebar-collapse-button — exists but unwired; now gets onclick JS +.is-collapsed on .dashboard-sidebar — triggers label/text hiding + + +type sidebarNavItem struct { + Href string + Label string + Icon string + Active bool + DividerAfter bool +} + + + + + + + + + + + Task 1: Update sidebarPrimaryNavItems and add team icon to SidebarNavIcon + backend/templates/app_layout_helpers.go, backend/templates/app_layout.templ + +In app_layout_helpers.go, replace the sidebarPrimaryNavItems function body per D-08. The new nav items are: + + func sidebarPrimaryNavItems(activePath string) []sidebarNavItem { + return []sidebarNavItem{ + {Href: "/", Label: "Home", Icon: "panels", Active: isActivePath(activePath, "/")}, + {Href: "#", Label: "My Tasks", Icon: "tasks", Active: false}, + {Href: "/", Label: "Projects", Icon: "layers", Active: false}, + {Href: "/planning", Label: "Events", Icon: "planning", Active: isActivePath(activePath, "/planning")}, + {Href: "#", Label: "Team Members", Icon: "team", Active: false}, + } + } + +Notes: +- "Projects" links to "/" (the tablos dashboard — no /tablos GET route exists per router.go). +- "My Tasks" and "Team Members" use href="#" (no route yet in v4.0 per D-08). +- DividerAfter is no longer needed (section labels replace dividers); remove it from all items. +- Chat and Files are removed entirely. + +In app_layout.templ, add a "team" case to the SidebarNavIcon switch, after the "files" case: + + case "team": + + +This is a standard Lucide "users" SVG — consistent with the existing icon set's stroke style. + + + cd /Users/arthur.belleville/Documents/perso/projects/xtablo-source/backend && templ generate ./... && go build ./... 2>&1 | tail -5 + + sidebarPrimaryNavItems returns 5 items (Home, My Tasks, Projects, Events, Team Members). SidebarNavIcon handles "team" without falling to default. Build passes. + + + + Task 2: Rebuild DashboardSidebar HTML with GENERAL/PROJECTS sections and collapse button + backend/templates/app_layout.templ + +Replace the DashboardSidebar templ component body with the new Figma-spec structure. The signature stays the same: + + templ DashboardSidebar(activePath string, tablos []sqlc.Tablo, user *auth.User, csrfToken string) + +New body structure: + + + +Key changes vs Phase 15: +- SidebarProjectsSection and SidebarOrganizationFooter components are no longer called from DashboardSidebar. SidebarOrganizationFooter (logout form) moves to the avatar dropdown in Plan 03. For now, remove it — Plan 03 adds it to the header dropdown. +- The PROJECTS section is inlined directly inside DashboardSidebar (the separate SidebarProjectsSection component still exists in the file for now; it simply won't be called). +- The HR separator before the projects section is removed; section labels replace dividers. +- Collapse button uses inline onclick JS that toggles 'sidebar-is-collapsed' on the .dashboard-shell element. No Alpine.js. No server round-trip. State resets on reload per D-09. +- The DividerAfter rendering branch in DashboardSidebar is removed (the sidebarNavItem struct field can stay in the struct for now; it just won't be used). + +Do NOT remove the SidebarOrganizationFooter, SidebarProjectsSection, or SidebarNavItemRow templ functions from the file — Plan 03 will clean up or repurpose them. + + + cd /Users/arthur.belleville/Documents/perso/projects/xtablo-source/backend && templ generate ./... && go build ./... 2>&1 | tail -5 + + DashboardSidebar no longer calls SidebarOrganizationFooter. New structure has sidebar-section-label divs for both General and Projects sections. Collapse button is wired. Build passes. + + + + Task 3: Add sidebar collapsed-state CSS to app.css + backend/internal/web/ui/app.css + +Locate the end of the existing sidebar CSS block in backend/internal/web/ui/app.css (currently ends around the .dashboard-main rule). Add the following new rules in a clearly labelled section after the existing sidebar block. Do NOT edit backend/static/tailwind.css directly — it is a compiled artifact. + +Add these rules: + + /* ── Sidebar collapse state ────────────────────────────────────────── */ + + /* When sidebar-is-collapsed is toggled on the shell, shrink the grid column */ + .dashboard-shell.sidebar-is-collapsed { + grid-template-columns: 4rem 1fr; + } + + /* Hide text labels and section headings in collapsed state */ + .dashboard-shell.sidebar-is-collapsed .sidebar-brand-title, + .dashboard-shell.sidebar-is-collapsed .sidebar-section-label, + .dashboard-shell.sidebar-is-collapsed .sidebar-nav-label, + .dashboard-shell.sidebar-is-collapsed .sidebar-project-label { + display: none; + } + + /* Center icons in collapsed state */ + .dashboard-shell.sidebar-is-collapsed .sidebar-nav-link-inner { + justify-content: center; + } + + /* Hide the full project list in collapsed state (icons not present for projects) */ + .dashboard-shell.sidebar-is-collapsed .sidebar-project-list { + display: none; + } + + /* Flip the collapse button chevron when collapsed */ + .dashboard-shell.sidebar-is-collapsed .sidebar-collapse-button svg { + transform: rotate(180deg); + } + + /* Ensure sidebar-section-label uses correct token styling if not already set */ + /* (Only add if not already defined above in the file) */ + .sidebar-section-label { + font-size: 0.6875rem; /* 11px */ + font-weight: 600; + letter-spacing: 0.08em; + text-transform: uppercase; + color: var(--color-text-muted); + padding: 0.75rem 0.75rem 0.25rem; + } + +After adding these rules, regenerate tailwind.css by running the CSS build command. The build pipeline is: + cd backend && npx tailwindcss -i tailwind.input.css -o static/tailwind.css --minify + +If the just/justfile command is available: `just build:css` or equivalent. Check the justfile for the exact command. The goal is to get the new CSS into the compiled tailwind.css that the browser loads. + +Verify the compiled file now contains the new classes: + grep -c "sidebar-is-collapsed" backend/static/tailwind.css +This must return a number greater than 0. + + + grep -c "sidebar-is-collapsed" /Users/arthur.belleville/Documents/perso/projects/xtablo-source/backend/static/tailwind.css + + app.css contains the sidebar-is-collapsed block. tailwind.css is regenerated and contains "sidebar-is-collapsed" class rules. go build ./... still passes. + + + + + +## Trust Boundaries + +| Boundary | Description | +|----------|-------------| +| Inline JS onclick → DOM | Collapse toggle runs inline script; no user input is processed | +| Tablo color field → CSS style attribute | tablo.Color.String is interpolated into a style attribute for the project icon color | + +## STRIDE Threat Register + +| Threat ID | Category | Component | Disposition | Mitigation Plan | +|-----------|----------|-----------|-------------|-----------------| +| T-18-02-01 | Tampering | tablo.Color in style attribute | accept | Color is stored in DB by the authenticated user themselves; templ does not sanitize style values but the content is user's own data | +| T-18-02-02 | Tampering | tailwind.css overwritten directly | mitigate | Always edit app.css; run build pipeline to regenerate tailwind.css — never hand-edit the artifact | +| T-18-02-03 | DoS | Sidebar collapse breaks layout (grid column not shrinking) | mitigate | CSS rule uses .dashboard-shell.sidebar-is-collapsed targeting the shell wrapper, not just the sidebar child — per Research Pitfall 3 | + + + +1. `cd backend && templ generate ./... && go build ./...` — zero errors +2. `grep -c "sidebar-is-collapsed" backend/static/tailwind.css` — returns > 0 +3. `grep "My Tasks\|Team Members\|GENERAL\|General" backend/templates/app_layout_helpers.go` — shows updated nav items +4. `cd backend && go test ./internal/web/ -run TestTablosDashboard_Sidebar -v` — existing test passes (class names .dashboard-sidebar and .sidebar-nav-shell are preserved) +5. Visual check: start server, visit dashboard — sidebar shows General section label, 5 nav items, Projects section label with tablo list; clicking collapse button shrinks sidebar to icon-only width + + + +- sidebarPrimaryNavItems returns exactly 5 items: Home, My Tasks, Projects, Events, Team Members +- Chat and Files items are gone from the nav +- DashboardSidebar renders GENERAL section label above nav items and PROJECTS section label above tablo list +- SidebarOrganizationFooter is no longer called from DashboardSidebar +- Collapse button is present and toggles sidebar-is-collapsed on .dashboard-shell via inline JS +- Collapsed state hides labels, shrinks grid column to 4rem +- go build ./... and go test ./... both pass + + + +After completion, create `.planning/phases/18-app-shell-navigation/18-02-SUMMARY.md` + diff --git a/.planning/phases/18-app-shell-navigation/18-03-PLAN.md b/.planning/phases/18-app-shell-navigation/18-03-PLAN.md new file mode 100644 index 0000000..ab9d660 --- /dev/null +++ b/.planning/phases/18-app-shell-navigation/18-03-PLAN.md @@ -0,0 +1,555 @@ +--- +phase: 18-app-shell-navigation +plan: 03 +type: execute +wave: 3 +depends_on: + - 18-01 + - 18-02 +files_modified: + - backend/templates/app_layout.templ + - backend/internal/web/ui/app.css + - backend/internal/web/handlers_tablos_test.go + - backend/internal/web/router.go +autonomous: true +requirements: + - NAV-01 + - NAV-02 + +must_haves: + truths: + - "Every authenticated page shows a top header bar with breadcrumb, search placeholder, and right-side icons" + - "Breadcrumb renders the correct path for each page (e.g., 'Dashboard' on the home page, 'Dashboard > Project Details' on tablo detail)" + - "Clicking the avatar circle opens a dropdown with workspace info, settings link, and logout button" + - "Logout from the avatar dropdown still works — POST /logout succeeds" + - "Settings link in dropdown navigates to /settings which renders a stub page" + - "go test ./... passes including updated sidebar test and new header test" + artifacts: + - path: "backend/templates/app_layout.templ" + provides: "PageHeader templ component with three zones and avatar dropdown" + contains: "PageHeader" + - path: "backend/internal/web/ui/app.css" + provides: "CSS for .page-header, .breadcrumb, .header-avatar-menu, .header-avatar-dropdown" + contains: "page-header" + - path: "backend/internal/web/handlers_tablos_test.go" + provides: "Updated TestTablosDashboard_Sidebar + new TestTablosDashboard_Header test" + contains: "page-header" + - path: "backend/internal/web/router.go" + provides: "/settings stub route" + contains: "/settings" + key_links: + - from: "backend/templates/app_layout.templ" + to: "backend/internal/web/ui/app.css" + via: ".page-header, .header-avatar-menu, .breadcrumb classes rendered in PageHeader" + pattern: "page-header" + - from: "backend/templates/app_layout.templ" + to: "/logout route" + via: "form method=POST action=/logout inside avatar dropdown, CSRF token via ui.CSRFField" + pattern: "action.*logout" + - from: "PageHeader" + to: "AppLayout" + via: "called inside dashboard-main div, above children" + pattern: "@PageHeader" +--- + + +Implement the PageHeader templ component (three-zone top bar: breadcrumb left, search placeholder center, bell/inbox/avatar right), wire it into AppLayout, add all supporting CSS to app.css, implement the avatar dropdown using native details/summary HTML, add a /settings stub route, and update tests to assert the new shell structure. + +Purpose: Delivers NAV-02. After this plan every authenticated page has the top header bar matching Figma, the avatar dropdown works, and logout continues to function. +Output: PageHeader component, avatar dropdown, /settings stub, updated tests, new CSS in app.css. + + + +@/Users/arthur.belleville/Documents/perso/projects/xtablo-source/.claude/get-shit-done/workflows/execute-plan.md +@/Users/arthur.belleville/Documents/perso/projects/xtablo-source/.claude/get-shit-done/templates/summary.md + + + +@.planning/ROADMAP.md +@.planning/phases/18-app-shell-navigation/18-CONTEXT.md +@.planning/phases/18-app-shell-navigation/18-RESEARCH.md +@.planning/phases/18-app-shell-navigation/18-01-SUMMARY.md +@.planning/phases/18-app-shell-navigation/18-02-SUMMARY.md + + + + + + + +templ AppLayout( + title string, + user *auth.User, + csrfToken string, + activePath string, + tablos []sqlc.Tablo, + pageTitle string, + breadcrumb []BreadcrumbItem, + headerActions templ.Component, +) + + +type BreadcrumbItem struct { + Label string + Href string +} + + +@ui.CSRFField(csrfToken) // renders a hidden input with CSRF token + + +type User struct { + ID uuid.UUID + Email string + PasswordHash pgtype.Text + CreatedAt time.Time + UpdatedAt time.Time +} +// Only Email is available. No Name or AvatarURL. + + +var(--color-surface-elevated) +var(--color-border-panel) +var(--color-text-muted) +var(--color-text-brand) +var(--shadow-floating-control) +var(--color-surface-elevated-strong) +var(--color-border-panel-muted) + + +backend/internal/web/handlers_tablos_test.go +// Existing test: TestTablosDashboard_Sidebar (line ~602) asserts "dashboard-sidebar" and "sidebar-nav-shell" + + + + + + Task 1: Implement PageHeader component and wire into AppLayout + backend/templates/app_layout.templ + +Add the PageHeader templ component to app_layout.templ. It renders the full-width top bar with three zones. Place the function after DashboardSidebar and before AppLayout in the file. + +PageHeader signature: + templ PageHeader(pageTitle string, breadcrumb []BreadcrumbItem, headerActions templ.Component, user *auth.User, csrfToken string) + +Structure: + + + +Wire PageHeader into AppLayout. In the AppLayout body, inside the dashboard-main div, call PageHeader BEFORE { children... }: + +
+ @PageHeader(pageTitle, breadcrumb, headerActions, user, csrfToken) + { children... } +
+ +Remove the earlier minimal breadcrumb stub that was added in Plan 01 (the `if len(breadcrumb) > 0 { }` and `if headerActions != nil { @headerActions }` blocks added as placeholders). The PageHeader call now handles both breadcrumb and headerActions rendering. + +Add a small inline script at the bottom of the body (after existing script tags) for the avatar dropdown outside-click close behavior: + + +
+ + cd /Users/arthur.belleville/Documents/perso/projects/xtablo-source/backend && templ generate ./... && go build ./... 2>&1 | tail -5 + + PageHeader component exists in app_layout.templ. AppLayout calls @PageHeader inside dashboard-main before children. Build passes. templ generate produces no errors. +
+ + + Task 2: Add page-header CSS to app.css, add /settings stub route, update tests + backend/internal/web/ui/app.css, backend/internal/web/router.go, backend/internal/web/handlers_tablos_test.go + +--- app.css: Add header bar CSS --- + +Append a new section after the sidebar collapse rules added in Plan 02: + + /* ── Page header bar ───────────────────────────────────────────────── */ + + .page-header { + display: flex; + align-items: center; + gap: 1rem; + padding: 0.75rem 1.5rem; + background: var(--color-surface-elevated); + border-bottom: 1px solid var(--color-border-panel); + margin: -2rem -2rem 1.5rem -2rem; /* cancel .dashboard-main padding on three sides, add bottom gap */ + } + + .page-header-left { + flex: 0 0 auto; + } + + .page-header-center { + flex: 1 1 auto; + display: flex; + justify-content: center; + } + + .page-header-right { + flex: 0 0 auto; + display: flex; + align-items: center; + gap: 0.5rem; + } + + .page-header-actions { + flex: 0 0 auto; + margin-left: auto; + } + + /* Breadcrumb */ + .breadcrumb { + display: flex; + align-items: center; + gap: 0.375rem; + font-size: 0.875rem; + } + + .breadcrumb-separator { + color: var(--color-text-muted); + user-select: none; + } + + .breadcrumb-item { + color: var(--color-text-muted); + font-weight: 500; + } + + .breadcrumb-item--link { + color: var(--color-text-brand); + text-decoration: none; + } + + .breadcrumb-item--link:hover { + text-decoration: underline; + } + + .breadcrumb-item--current { + color: var(--color-text-muted); + } + + /* Search placeholder */ + .header-search-placeholder { + display: flex; + align-items: center; + gap: 0.5rem; + padding: 0.375rem 0.75rem; + border: 1px solid var(--color-border-panel); + border-radius: 0.375rem; + background: var(--color-surface-muted-inverse); + color: var(--color-text-muted); + font-size: 0.875rem; + min-width: 16rem; + max-width: 28rem; + width: 100%; + cursor: default; + } + + /* Icon buttons (bell, inbox) */ + .header-icon-button { + display: flex; + align-items: center; + justify-content: center; + width: 2rem; + height: 2rem; + border-radius: 0.375rem; + border: none; + background: transparent; + color: var(--color-text-muted); + cursor: pointer; + } + + .header-icon-button:hover { + background: var(--overlay-dark-soft); + } + + .header-icon-button:disabled { + opacity: 0.5; + cursor: not-allowed; + } + + /* Avatar dropdown */ + .header-avatar-menu { + position: relative; + } + + .header-avatar-menu > summary { + list-style: none; + cursor: pointer; + } + + .header-avatar-menu > summary::-webkit-details-marker { + display: none; + } + + .header-avatar-button { + display: flex; + align-items: center; + justify-content: center; + width: 2rem; + height: 2rem; + border-radius: 50%; + background: var(--color-text-brand); + color: #fff; + font-size: 0.8125rem; + font-weight: 600; + cursor: pointer; + border: none; + outline: none; + } + + .header-avatar-initial { + text-transform: uppercase; + line-height: 1; + pointer-events: none; + } + + .header-avatar-dropdown { + position: absolute; + right: 0; + top: calc(100% + 0.5rem); + min-width: 14rem; + background: var(--color-surface-elevated); + border: 1px solid var(--color-border-panel); + border-radius: 0.5rem; + box-shadow: var(--shadow-floating-control); + z-index: 50; + overflow: hidden; + } + + .avatar-dropdown-workspace { + padding: 0.75rem 1rem; + display: flex; + flex-direction: column; + gap: 0.125rem; + } + + .avatar-dropdown-workspace-name { + font-size: 0.875rem; + font-weight: 600; + color: var(--color-text-muted); + word-break: break-all; + } + + .avatar-dropdown-workspace-meta { + font-size: 0.75rem; + color: var(--color-text-muted); + opacity: 0.7; + } + + .avatar-dropdown-divider { + border: none; + border-top: 1px solid var(--color-border-panel-muted); + margin: 0; + } + + .avatar-dropdown-item { + display: block; + width: 100%; + padding: 0.625rem 1rem; + font-size: 0.875rem; + color: var(--color-text-muted); + text-decoration: none; + background: transparent; + border: none; + text-align: left; + cursor: pointer; + } + + .avatar-dropdown-item:hover { + background: var(--overlay-dark-soft); + } + + .avatar-dropdown-item--danger { + color: #dc2626; /* red — logout action; no token for this; matches Figma red logout */ + } + + .avatar-dropdown-item--danger:hover { + background: #fef2f2; + } + + .avatar-dropdown-logout-form { + display: contents; /* form element itself doesn't affect layout */ + } + +After adding these rules, regenerate tailwind.css: + cd backend && npx tailwindcss -i tailwind.input.css -o static/tailwind.css --minify + +(Use the same CSS build command identified in Plan 02.) + +--- router.go: Add /settings stub --- + +In the authenticated routes block (inside the auth middleware group), add a GET handler for /settings that renders a simple stub page. Since no full settings template exists, use an inline http.HandlerFunc that writes a minimal HTML response: + + r.Get("/settings", func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "text/html; charset=utf-8") + _, _ = w.Write([]byte(`Settings - Xtablo

Settings

Coming soon.

Back to dashboard`)) + }) + +Place this route near the other single-page GET routes (e.g., next to /account/providers). This stub satisfies the D-06 requirement that the settings link navigates somewhere. A full settings page is deferred. + +--- handlers_tablos_test.go: Update existing test and add header test --- + +Locate TestTablosDashboard_Sidebar (around line 602). This test currently asserts "dashboard-sidebar" and "sidebar-nav-shell". These class names are preserved by the rebuild, so the test already passes. Add two new assertions to this test: + +1. Check that "page-header" appears in the response body (the new header bar is present). +2. Check that "breadcrumb" appears in the response body. + +Example using the existing assertion pattern: + if !strings.Contains(body, "page-header") { + t.Errorf("expected page-header class in response body") + } + if !strings.Contains(body, "breadcrumb") { + t.Errorf("expected breadcrumb class in response body") + } + +Also add a new standalone test named TestTablosDashboard_Header that: +- Calls the TablosListHandler (same setup as TestTablosDashboard_Sidebar) +- Asserts the response body contains "page-header" +- Asserts the response body contains "header-avatar-menu" (the avatar dropdown element) +- Asserts the response body contains "Dashboard" (the breadcrumb label for the home page) +- Asserts the response body contains "header-search-placeholder" + +Pattern: copy the setup from TestTablosDashboard_Sidebar — same mock setup, same request/response recorder pattern. +
+ + cd /Users/arthur.belleville/Documents/perso/projects/xtablo-source/backend && grep -c "page-header" static/tailwind.css && go test ./internal/web/ -run "TestTablosDashboard" -v 2>&1 | tail -20 + + tailwind.css contains "page-header" CSS. TestTablosDashboard_Sidebar passes with new assertions. TestTablosDashboard_Header exists and passes. /settings route is registered and returns 200. Logout from avatar dropdown POSTs to /logout and succeeds. +
+ +
+ + +## Trust Boundaries + +| Boundary | Description | +|----------|-------------| +| Client → POST /logout | Logout is a state-changing POST; CSRF token guards against cross-site forgery | +| Avatar dropdown → DOM | Dropdown contains a logout form; clicking outside closes it via JS listener | +| user.Email → HTML | Email rendered inside dropdown; templ auto-escapes it | + +## STRIDE Threat Register + +| Threat ID | Category | Component | Disposition | Mitigation Plan | +|-----------|----------|-----------|-------------|-----------------| +| T-18-03-01 | Cross-site request forgery | POST /logout from avatar dropdown | mitigate | ui.CSRFField(csrfToken) in the logout form — same pattern as the original SidebarOrganizationFooter logout form | +| T-18-03-02 | Tampering | user.Email rendered in dropdown | accept | templ auto-escapes all string interpolations; no raw HTML insertion possible | +| T-18-03-03 | Information disclosure | /settings stub exposes that settings page exists | accept | Page is behind authentication middleware; no sensitive data exposed | +| T-18-03-04 | Elevation of privilege | nil headerActions panic | mitigate | Plan 01 stub guard replaced by PageHeader's own nil-guard before @headerActions | + + + +1. `cd backend && templ generate ./... && go build ./...` — zero errors +2. `grep -c "page-header" backend/static/tailwind.css` — returns > 0 +3. `grep -c "header-avatar-menu" backend/static/tailwind.css` — returns > 0 +4. `cd backend && go test ./... 2>&1 | tail -10` — all tests pass including TestTablosDashboard_Header +5. `cd backend && go test ./internal/web/ -run TestTablosDashboard_Header -v` — explicitly verify new test exists and passes +6. Manual: Start server, visit dashboard — top header bar visible with breadcrumb "Dashboard", search placeholder, bell and inbox placeholders, avatar circle +7. Manual: Click avatar circle — dropdown opens showing email, Settings link, Log out button (red text) +8. Manual: Click Log out — redirects to login page (POST /logout succeeds) +9. Manual: Visit a tablo detail page — breadcrumb shows "Dashboard > [tablo name]" +10. Manual: Click Settings in dropdown — navigates to /settings, shows "Coming soon" stub + + + +- PageHeader component renders in every authenticated page (verified by page-header class in test) +- Breadcrumb shows correct labels per page (Dashboard on home, Dashboard > title on tablo detail, Planning on /planning) +- Avatar dropdown uses details/summary HTML — no Alpine.js +- Avatar dropdown contains: email as workspace name, Settings link to /settings, Logout form with CSRF +- Logout POST succeeds from the avatar dropdown +- /settings route returns 200 with stub content +- TestTablosDashboard_Sidebar still passes (existing class names preserved) +- TestTablosDashboard_Header passes (new assertions for page-header, breadcrumb, header-avatar-menu) +- go build ./... and go test ./... both pass +- tailwind.css contains page-header and header-avatar-dropdown CSS + + + +After completion, create `.planning/phases/18-app-shell-navigation/18-03-SUMMARY.md` + diff --git a/.planning/phases/18-app-shell-navigation/18-VERIFICATION.md b/.planning/phases/18-app-shell-navigation/18-VERIFICATION.md new file mode 100644 index 0000000..41a629e --- /dev/null +++ b/.planning/phases/18-app-shell-navigation/18-VERIFICATION.md @@ -0,0 +1,148 @@ +--- +phase: 18-app-shell-navigation +verified: 2026-05-17T00:00:00Z +status: human_needed +score: 8/9 must-haves verified +overrides_applied: 0 +human_verification: + - test: "Visit the dashboard at http://localhost:8080 and inspect the sidebar" + expected: "Sidebar shows 'General' section label above 5 nav items (Home, My Tasks, Projects, Events, Team Members), 'Projects' section label above tablo list, and collapse button that shrinks sidebar to icon-only on click" + why_human: "Visual Figma match requires rendering the browser; CSS collapse behavior is JavaScript-driven and cannot be verified without a live browser" + - test: "Click the avatar circle in the top header bar" + expected: "Dropdown opens showing user email, a Settings link, and a red Log out button; clicking outside closes it" + why_human: "Native details/summary open/close behavior and outside-click JS listener require live browser interaction" + - test: "Click Log out in the avatar dropdown" + expected: "POST /logout is submitted with CSRF token, session is destroyed, user is redirected to login" + why_human: "End-to-end logout flow requires a running server with an active session" + - test: "Navigate to a tablo detail page" + expected: "Breadcrumb reads 'Dashboard > [Tablo Title]', not just 'Dashboard'" + why_human: "Requires live server and a real tablo in the database to verify dynamic breadcrumb rendering" + - test: "Navigate to /settings" + expected: "Returns 200 with 'Coming soon' stub page and no 500 error" + why_human: "Requires a running server to verify the inline handler response" +--- + +# Phase 18: App Shell & Navigation Verification Report + +**Phase Goal:** Redesign the sidebar and top header bar to match the Figma design. +**Verified:** 2026-05-17 +**Status:** human_needed +**Re-verification:** No — initial verification + +## Goal Achievement + +### Observable Truths + +| # | Truth | Status | Evidence | +|---|-------|--------|----------| +| 1 | The Go codebase compiles with zero errors after AppLayout signature is extended | VERIFIED | `go build ./...` exits 0 with no output | +| 2 | Every authenticated page still renders (no 500 errors) | UNCERTAIN (human) | Build passes; runtime smoke test requires live server | +| 3 | go test ./... passes with no failures | VERIFIED | All packages pass: internal/auth, internal/db, internal/files, internal/jobs, internal/web, internal/web/ui, templates | +| 4 | Sidebar shows a GENERAL section label above Home, My Tasks, Projects, Events, Team Members nav items | VERIFIED | `DashboardSidebar` in `app_layout.templ` lines 151-157: `sidebar-section-label` div with text "General", `sidebarPrimaryNavItems` returns exactly 5 items | +| 5 | Sidebar shows a PROJECTS section label above the tablo list | VERIFIED | `app_layout.templ` lines 161-177: inner `sidebar-section-label` div with text "Projects" above tablo loop | +| 6 | Chat and Files nav items are removed from the sidebar | VERIFIED | `sidebarPrimaryNavItems` in `app_layout_helpers.go` returns only: Home, My Tasks, Projects, Events, Team Members — no Chat or Files | +| 7 | Clicking the collapse button toggles the sidebar between full-width and icon-only states | UNCERTAIN (human) | Collapse button has correct inline `onclick` JS toggling `sidebar-is-collapsed` on `.dashboard-shell`; `tailwind.css` contains 5 occurrences of `sidebar-is-collapsed` CSS rules — requires browser to verify visual behavior | +| 8 | Every authenticated page shows a top header bar with breadcrumb, search placeholder, and right-side icons | VERIFIED | `PageHeader` component exists in `app_layout.templ` lines 186-267; called from `AppLayout` line 290 inside `.dashboard-main` before `{ children... }` | +| 9 | Breadcrumb renders the correct path for each page | VERIFIED | All 10 handler call sites in handlers_tablos.go, handlers_planning.go, handlers_account.go, handlers_discussion.go, handlers_files.go, handlers_events.go pass `[]templates.BreadcrumbItem` with correct labels and hrefs | +| 10 | Clicking the avatar circle opens a dropdown with workspace info, settings link, and logout button | UNCERTAIN (human) | `details.header-avatar-menu` with `summary` and dropdown div confirmed in `app_layout.templ` lines 237-257; outside-click JS listener at lines 298-302; requires live browser | +| 11 | Logout from the avatar dropdown still works — POST /logout succeeds | UNCERTAIN (human) | Logout `
` with `@ui.CSRFField(csrfToken)` is present in `PageHeader` (line 252-255); requires live server to verify end-to-end | +| 12 | Settings link in dropdown navigates to /settings which renders a stub page | VERIFIED | `/settings` route found at `router.go:90`; inline stub handler returning HTML with "Coming soon" content | +| 13 | go test ./... passes including updated sidebar test and new header test | VERIFIED | All packages pass; `TestTablosDashboard_Header` function confirmed at `handlers_tablos_test.go:647`; assertions for `page-header`, `header-avatar-menu`, `Dashboard`, `header-search-placeholder` all present | + +**Score:** 8/9 automated truths verified (4 require human browser/server testing) + +### Required Artifacts + +| Artifact | Expected | Status | Details | +|----------|----------|--------|---------| +| `backend/templates/app_layout_helpers.go` | BreadcrumbItem struct export | VERIFIED | Lines 6-10: `type BreadcrumbItem struct { Label string; Href string }` | +| `backend/templates/app_layout_helpers.go` | sidebarPrimaryNavItems with Figma-spec items | VERIFIED | Lines 55-63: 5 items, "My Tasks" and "Team Members" present, Chat/Files absent | +| `backend/templates/app_layout.templ` | Extended AppLayout signature (8 params) | VERIFIED | Line 277: `templ AppLayout(title string, user *auth.User, csrfToken string, activePath string, tablos []sqlc.Tablo, pageTitle string, breadcrumb []BreadcrumbItem, headerActions templ.Component)` | +| `backend/templates/app_layout.templ` | DashboardSidebar with GENERAL/PROJECTS sections and collapse button | VERIFIED | Lines 128-181: sections present, inline onclick JS on collapse button at lines 139-147 | +| `backend/templates/app_layout.templ` | PageHeader component with three zones and avatar dropdown | VERIFIED | Lines 186-267: breadcrumb left, search center, bell/inbox/avatar right, details/summary dropdown | +| `backend/internal/web/ui/app.css` | is-collapsed CSS rules | VERIFIED | Lines 333-358: `.dashboard-shell.sidebar-is-collapsed` rules covering grid-template-columns, label hiding, icon centering, project-list hiding, chevron flip | +| `backend/internal/web/ui/app.css` | page-header CSS block | VERIFIED | Lines 362-567: complete `.page-header`, `.breadcrumb`, `.header-avatar-menu`, `.header-avatar-dropdown` CSS | +| `backend/internal/web/handlers_tablos_test.go` | TestTablosDashboard_Header test | VERIFIED | Lines 647-683: function exists with assertions for page-header, header-avatar-menu, Dashboard, header-search-placeholder | +| `backend/internal/web/router.go` | /settings stub route | VERIFIED | Line 90: `r.Get("/settings", ...)` inline handler | + +### Key Link Verification + +| From | To | Via | Status | Details | +|------|----|-----|--------|---------| +| `handlers_tablos.go` | `templates/tablos.templ` | `TablosDashboard / TabloDetailPage` with `BreadcrumbItem` args | VERIFIED | Grep confirms `[]templates.BreadcrumbItem{...}` at lines 62, 232, 350, 480 | +| `app_layout_helpers.go` | `app_layout.templ` | `sidebarPrimaryNavItems` called in `DashboardSidebar` | VERIFIED | `app_layout.templ` line 153: `for _, item := range sidebarPrimaryNavItems(activePath)` | +| `app_layout.templ` | `app.css` | `sidebar-is-collapsed` toggled by inline JS, CSS rule picks it up | VERIFIED | Both sides confirmed; CSS in app.css and tailwind.css (5 occurrences) | +| `app_layout.templ` | `app.css` | `.page-header` class rendered in `PageHeader`, CSS defines it | VERIFIED | `tailwind.css` has 5 occurrences of `page-header`; 3 occurrences of `header-avatar-menu` | +| `PageHeader` | `AppLayout` | `@PageHeader(...)` called inside `dashboard-main` | VERIFIED | `app_layout.templ` line 290: `@PageHeader(pageTitle, breadcrumb, headerActions, user, csrfToken)` | +| `app_layout.templ` | `/logout` route | `form method=POST action=/logout` with `@ui.CSRFField` | VERIFIED | `app_layout.templ` lines 252-255: form present with CSRF field | +| `handlers_planning.go` | `templates/planning.templ` | `PlanningPage` with breadcrumb args | VERIFIED | `handlers_planning.go:70` contains `[]templates.BreadcrumbItem{{Label: "Planning", Href: ""}}` | +| `handlers_account.go` | `templates/account_providers.templ` | `AccountProvidersPage` with breadcrumb args | VERIFIED | `handlers_account.go:51` contains `[]templates.BreadcrumbItem{{Label: "Linked Providers", Href: ""}}` | +| `handlers_discussion.go` | `templates/tablos.templ` | `TabloDetailPage` with breadcrumb args | VERIFIED | `handlers_discussion.go:82` contains `[]templates.BreadcrumbItem{{Label: "Dashboard", Href: "/"}, {Label: tablo.Title, Href: ""}}` | +| `handlers_files.go` | `templates/tablos.templ` | `TabloDetailPage` with breadcrumb args (2 call sites) | VERIFIED | `handlers_files.go:110,146` both contain breadcrumb args | +| `handlers_events.go` | `templates/tablos.templ` | `TabloDetailPage` with breadcrumb args | VERIFIED | `handlers_events.go:181` contains breadcrumb args | + +### Requirements Coverage + +| Requirement | Source Plan | Description | Status | Evidence | +|-------------|-------------|-------------|--------|----------| +| NAV-01 | 18-01, 18-02, 18-03 | User sees a redesigned sidebar/nav bar matching Figma (brand section, icon nav items, tablo list section, user footer) | VERIFIED (code) / NEEDS HUMAN (visual match) | `DashboardSidebar` rebuilt with two-section structure; brand/logo at lines 133-147; nav items at 153-157; tablo list at 164-177. SidebarOrganizationFooter moved to avatar dropdown in header — user info accessible via header not sidebar footer. Visual Figma match requires human. | +| NAV-02 | 18-01, 18-03 | User sees a per-page top header bar with page title and contextual action buttons matching Figma | VERIFIED (code) / NEEDS HUMAN (visual match) | `PageHeader` component at `app_layout.templ:186-267`; breadcrumb wired from all 10 handler call sites; `headerActions` slot present with nil-guard | + +### Anti-Patterns Found + +| File | Line | Pattern | Severity | Impact | +|------|------|---------|----------|--------| +| `backend/templates/app_layout.templ` | 109-122 | `SidebarOrganizationFooter` component defined but never called | Info | Dead code — intentional per Plan 02/03 decisions; safe to remove in a cleanup pass. Not a blocker. | +| `backend/templates/app_layout.templ` | 86-105 | `SidebarProjectsSection` component defined but never called | Info | Dead code — inlined directly into `DashboardSidebar` per Plan 02; safe to remove in cleanup. Not a blocker. | + +No TBD, FIXME, or XXX markers found in phase-modified files. + +### Human Verification Required + +#### 1. Sidebar Visual Figma Match + +**Test:** Start the server (`cd backend && go run . serve`), navigate to `http://localhost:8080`, inspect the sidebar. +**Expected:** Sidebar shows brand/logo section at top, "General" section label above 5 nav items (Home, My Tasks, Projects, Events, Team Members), "Projects" section label above the tablo list. Clicking the collapse button shrinks the sidebar to icon-only (~4rem) width and hides all text labels. +**Why human:** CSS collapse behavior is JavaScript-driven; the visual Figma match cannot be verified programmatically. + +#### 2. Avatar Dropdown Interaction + +**Test:** Click the user avatar circle in the top right of the header bar. +**Expected:** A dropdown opens showing the user email at the top, a "Settings" link, and a red "Log out" button. Clicking outside the dropdown closes it. +**Why human:** Native `
/` open/close behavior and the outside-click JS listener require live browser interaction. + +#### 3. Logout Flow from Avatar Dropdown + +**Test:** Click "Log out" in the avatar dropdown while authenticated. +**Expected:** A POST request is sent to `/logout` with a valid CSRF token, the session is destroyed, and the user is redirected to the login page. +**Why human:** Requires a running server with an active authenticated session. + +#### 4. Tablo Detail Breadcrumb + +**Test:** Navigate to a tablo detail page (`/tablos/{id}`). +**Expected:** The breadcrumb in the top header shows "Dashboard > [Tablo Title]" where "Dashboard" is a clickable link back to `/` and "[Tablo Title]" is plain text. +**Why human:** Requires a live server and a real tablo in the database to verify dynamic breadcrumb rendering. + +#### 5. /settings Stub + +**Test:** Navigate to `/settings` (authenticated). +**Expected:** Returns a 200 response with "Coming soon" text and a back link to the dashboard. No 500 error. +**Why human:** Requires a running server; the handler is an inline `http.HandlerFunc`, not a full template-rendered page. + +### Gaps Summary + +No gaps blocking goal achievement. All automated checks passed: +- `go build ./...` succeeds with zero errors +- `go test ./...` passes across all packages +- All 10 handler call sites supply `BreadcrumbItem` args +- `PageHeader` component exists and is wired into `AppLayout` +- `/settings` route is registered +- `TestTablosDashboard_Header` test exists with all required assertions +- Collapsed sidebar CSS rules are in both `app.css` and compiled `tailwind.css` + +The 5 human verification items are runtime/visual checks that cannot be validated by static analysis alone. They are expected outcomes of the implementation — the code wiring is complete. Deferred dead code (`SidebarOrganizationFooter`, `SidebarProjectsSection` unused components) is informational only and does not block the phase goal. + +--- + +_Verified: 2026-05-17_ +_Verifier: Claude (gsd-verifier)_