---
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"
---

<objective>
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.
</objective>

<execution_context>
@/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
</execution_context>

<context>
@.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
</context>

<interfaces>
<!-- Read screenshots/Homepage.png and screenshots/Board.png before implementing visual details -->
<!-- They are at /Users/arthur.belleville/Documents/perso/projects/xtablo-source/screenshots/ -->

<!-- Current DashboardSidebar signature (unchanged in Plan 01) -->
templ DashboardSidebar(activePath string, tablos []sqlc.Tablo, user *auth.User, csrfToken string)

<!-- Existing CSS classes to preserve (used in existing tests) -->
.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)

<!-- Existing CSS classes to update/add in app.css -->
.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

<!-- sidebarNavItem struct (unchanged) -->
type sidebarNavItem struct {
    Href         string
    Label        string
    Icon         string
    Active       bool
    DividerAfter bool
}

<!-- Existing icon kinds in SidebarNavIcon: panels, tasks, layers, planning, chat, files -->
<!-- "layers" is the icon for Projects per Figma (stacked layers = projects/tablos) -->
<!-- "tasks" exists and maps to My Tasks -->
<!-- No "team" icon exists — add a new "team" case to SidebarNavIcon using a users SVG -->
</interfaces>

<tasks>

<task type="auto">
  <name>Task 1: Update sidebarPrimaryNavItems and add team icon to SidebarNavIcon</name>
  <files>backend/templates/app_layout_helpers.go, backend/templates/app_layout.templ</files>
  <action>
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":
        <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
            <path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"></path>
            <circle cx="9" cy="7" r="4"></circle>
            <path d="M22 21v-2a4 4 0 0 0-3-3.87"></path>
            <path d="M16 3.13a4 4 0 0 1 0 7.75"></path>
        </svg>

This is a standard Lucide "users" SVG — consistent with the existing icon set's stroke style.
  </action>
  <verify>
    <automated>cd /Users/arthur.belleville/Documents/perso/projects/xtablo-source/backend && templ generate ./... && go build ./... 2>&1 | tail -5</automated>
  </verify>
  <done>sidebarPrimaryNavItems returns 5 items (Home, My Tasks, Projects, Events, Team Members). SidebarNavIcon handles "team" without falling to default. Build passes.</done>
</task>

<task type="auto">
  <name>Task 2: Rebuild DashboardSidebar HTML with GENERAL/PROJECTS sections and collapse button</name>
  <files>backend/templates/app_layout.templ</files>
  <action>
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:

    <aside class="dashboard-sidebar">
        <nav aria-label="Main navigation" class="sidebar-nav-shell">
            <!-- Brand / logo -->
            <div class="sidebar-brand">
                <a class="sidebar-brand-link" href="/" aria-label="Home">
                    <img class="sidebar-brand-logo" src="/static/logo_dark.png" alt="Logo XTablo"/>
                    <h1 class="sidebar-brand-title">XTablo</h1>
                </a>
                <button
                    class="sidebar-collapse-button"
                    aria-label="Toggle sidebar"
                    onclick="(function(btn){var shell=document.querySelector('.dashboard-shell');if(shell){shell.classList.toggle('sidebar-is-collapsed');}})(this)"
                    type="button"
                >
                    <!-- chevron-left icon (collapses) — use existing icon or inline SVG -->
                    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" width="16" height="16">
                        <path d="m15 18-6-6 6-6"/>
                    </svg>
                </button>
            </div>

            <!-- GENERAL section -->
            <div class="sidebar-primary">
                <div class="sidebar-section-label">General</div>
                <ul class="sidebar-list" role="list">
                    for _, item := range sidebarPrimaryNavItems(activePath) {
                        <li>
                            @SidebarNavItemRow(item)
                        </li>
                    }
                </ul>

                <!-- PROJECTS section (tablo list) -->
                <div class="sidebar-projects">
                    <div class="sidebar-section-label">Projects</div>
                    <ul class="sidebar-project-list">
                        for _, tablo := range tablos {
                            <li>
                                <a href={ templ.SafeURL("/tablos/" + tablo.ID.String()) } class="sidebar-project-link">
                                    if tablo.Color.Valid && tablo.Color.String != "" {
                                        <span class="sidebar-project-icon" style={ "background-color: " + tablo.Color.String }></span>
                                    } else {
                                        <span class="sidebar-project-icon"></span>
                                    }
                                    <span class="sidebar-project-label">{ tablo.Title }</span>
                                </a>
                            </li>
                        }
                    </ul>
                </div>
            </div>
        </nav>
    </aside>

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.
  </action>
  <verify>
    <automated>cd /Users/arthur.belleville/Documents/perso/projects/xtablo-source/backend && templ generate ./... && go build ./... 2>&1 | tail -5</automated>
  </verify>
  <done>DashboardSidebar no longer calls SidebarOrganizationFooter. New structure has sidebar-section-label divs for both General and Projects sections. Collapse button is wired. Build passes.</done>
</task>

<task type="auto">
  <name>Task 3: Add sidebar collapsed-state CSS to app.css</name>
  <files>backend/internal/web/ui/app.css</files>
  <action>
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.
  </action>
  <verify>
    <automated>grep -c "sidebar-is-collapsed" /Users/arthur.belleville/Documents/perso/projects/xtablo-source/backend/static/tailwind.css</automated>
  </verify>
  <done>app.css contains the sidebar-is-collapsed block. tailwind.css is regenerated and contains "sidebar-is-collapsed" class rules. go build ./... still passes.</done>
</task>

</tasks>

<threat_model>
## 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 |
</threat_model>

<verification>
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
</verification>

<success_criteria>
- 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
</success_criteria>

<output>
After completion, create `.planning/phases/18-app-shell-navigation/18-02-SUMMARY.md`
</output>
