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

<objective>
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.
</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/REQUIREMENTS.md
@.planning/phases/18-app-shell-navigation/18-CONTEXT.md
@.planning/phases/18-app-shell-navigation/18-RESEARCH.md
</context>

<interfaces>
<!-- Current AppLayout signature (to be replaced) -->
<!-- From backend/templates/app_layout.templ line 153 -->
templ AppLayout(title string, user *auth.User, csrfToken string, activePath string, tablos []sqlc.Tablo)

<!-- Current templ call sites that wrap AppLayout -->
<!-- tablos.templ line 12 -->
templ TablosDashboard(user *auth.User, csrfToken string, activePath string, tablos []sqlc.Tablo, cards []TabloCardView)

<!-- tablos.templ line 241 -->
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)

<!-- planning.templ line 8 -->
templ PlanningPage(user *auth.User, csrfToken string, activePath string, tablos []sqlc.Tablo, agenda PlanningAgenda)

<!-- account_providers.templ line 8 -->
templ AccountProvidersPage(user *auth.User, providers []LinkedProviderStatus, csrfToken string, activePath string, tablos []sqlc.Tablo)

<!-- Handler call sites (handlers_tablos.go) -->
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")

<!-- Handler call sites (other handlers) -->
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")
</interfaces>

<tasks>

<task type="auto">
  <name>Task 1: Define BreadcrumbItem and extend AppLayout signature</name>
  <files>backend/templates/app_layout_helpers.go, backend/templates/app_layout.templ</files>
  <action>
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 {
        <!-- breadcrumb: Plan 03 will render this properly -->
    }
    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.
  </action>
  <verify>
    <automated>cd /Users/arthur.belleville/Documents/perso/projects/xtablo-source/backend && templ generate ./... 2>&1 | head -20</automated>
  </verify>
  <done>app_layout_helpers.go exports BreadcrumbItem struct. app_layout.templ AppLayout signature has 8 parameters. templ generate produces no errors.</done>
</task>

<task type="auto">
  <name>Task 2: Update all templ wrapper signatures and their internal AppLayout calls</name>
  <files>backend/templates/tablos.templ, backend/templates/planning.templ, backend/templates/account_providers.templ</files>
  <action>
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.
  </action>
  <verify>
    <automated>cd /Users/arthur.belleville/Documents/perso/projects/xtablo-source/backend && templ generate ./... 2>&1 | tail -5</automated>
  </verify>
  <done>All three templ files compile without errors. TablosDashboard, TabloDetailPage, PlanningPage, and AccountProvidersPage all accept and pass through pageTitle and breadcrumb.</done>
</task>

<task type="auto">
  <name>Task 3: Update all handler call sites with per-page breadcrumbs</name>
  <files>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</files>
  <action>
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.
  </action>
  <verify>
    <automated>cd /Users/arthur.belleville/Documents/perso/projects/xtablo-source/backend && go build ./... && go test ./... 2>&1 | tail -20</automated>
  </verify>
  <done>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.</done>
</task>

</tasks>

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

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

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

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