docs(15): research phase domain

This commit is contained in:
Arthur Belleville 2026-05-16 21:17:03 +02:00
parent 65c17561ae
commit 8422d82fc8
No known key found for this signature in database

View file

@ -0,0 +1,592 @@
# Phase 15: Dashboard & Tablos - Research
**Researched:** 2026-05-16
**Domain:** Go templ layout shell + CSS porting + HTMX dashboard
**Confidence:** HIGH
## Summary
Phase 15 replaces the current top-nav `Layout` with a sidebar-based `AppLayout` and reskins the tablo dashboard as a project-card grid. The go-backend (`go-backend/internal/web/views/dashboard_components.templ` + `go-backend/internal/web/ui/app.css`) is the canonical visual reference and source of CSS to port. All architecture decisions are locked in CONTEXT.md — this research confirms their technical feasibility and documents the exact files to touch, Go patterns to follow, and pitfalls to avoid.
The primary work splits into three parallel tracks: (1) new `AppLayout` templ component in `backend/templates/`, (2) CSS extraction from `go-backend/internal/web/ui/app.css` into a new `backend/internal/web/ui/app.css`, and (3) restyled `TablosDashboard` + `TablosEmptyState` + `TabloCard` in `backend/templates/tablos.templ`. Handler changes are minimal — only the signature of `TablosDashboard` call sites changes.
**Primary recommendation:** Port the CSS sections verbatim from go-backend's app.css (lines 455743 cover all sidebar CSS; lines 8941046 cover project-card CSS). Then create `AppLayout` following the `AuthLayout` pattern already established in Phase 14.
---
<user_constraints>
## User Constraints (from CONTEXT.md)
### Locked Decisions
#### App Layout Shell
- **D-L01:** Create a new `AppLayout(title string, user *auth.User, csrfToken string, activePath string, tablos []sqlc.Tablo)` templ in `backend/templates/`. All authenticated dashboard pages use `AppLayout`. The old `Layout` templ is preserved but no longer used by dashboard pages (it becomes dead code, can be removed in a future cleanup).
- **D-L02:** `AppLayout` uses the `.dashboard-shell` CSS grid (sidebar + main columns) from `go-backend/internal/web/ui/app.css`. Port `.dashboard-shell`, `.dashboard-sidebar`, `.sidebar-nav-shell`, all `.sidebar-*` CSS, `.sidebar-organization`, and `.dashboard-main` into `backend/internal/web/ui/app.css` (a new CSS file, not auth.css).
- **D-L03:** Active nav item: `AppLayout` receives `activePath string`. A helper function (pure Go, not templ) returns `"is-active"` class when a nav item's href matches `activePath`. Handlers pass the current route path explicitly (e.g., `"/"`, `"/planning"`, `"/tablos"`).
- **D-L04:** Scripts: `AppLayout` loads `htmx.min.js`, `sortable.min.js`, and `discussion-sse.js` — same as the current `Layout`. No change to script loading.
#### Sidebar Nav Items
- **D-N01:** Match go-backend nav item set: Dashboard (`/`), Tablos (`/` or tablo overview — same as Dashboard for now), Tasks, Planning (`/planning`), Chat, Files, Settings. Use go-backend's `dashboard_components.templ` icon SVGs verbatim.
- **D-N02:** Routes that exist in the backend get real `href` values: Dashboard (`/`), Planning (`/planning`). Routes not yet built as standalone pages (Tasks, Files, Settings) render as visual nav items with no `href` attribute (display only, `cursor: default` on the item).
- **D-N03:** Chat is per-tablo. In the sidebar nav, Chat links to `/` (dashboard) as a placeholder, or is rendered as a visual-only item like Tasks — Claude's discretion.
- **D-N04:** Sidebar-projects section: shows the user's tablos as a list with a colored circle icon (derived from tablo color field) + truncated title. Each item links to `/tablos/{id}`. Section label: "Projects" (English, matching backend convention).
#### Tablo Project Cards
- **D-C01:** Tablo dashboard uses the `.project-grid` 3-column CSS grid layout from go-backend. Port `.project-grid`, `.project-card`, `.project-card-top`, and related CSS into the new `app.css`.
- **D-C02:** Each project card shows (matching go-backend exactly): colored circle avatar (top-left) + title + edit icon button (top-right) + delete icon button (top-right) + creation date (bottom row).
- **D-C03:** Color avatar: use `tablo.Color` field when set. When null/empty, show a default neutral/gray circle (no derived color, no initials). The circle renders via a small `<span>` styled with background-color, matching go-backend's `.sidebar-project-icon` pattern.
- **D-C04:** Edit and delete action icons use `@ui.IconButton(...)` from the design system (Phase 13 component). The HTMX attributes for edit (`hx-get=/tablos/{id}/edit`) and delete (`hx-delete=/tablos/{id}`) are preserved from the current `TabloCard` implementation.
- **D-C05:** The "New tablo" button and inline create form slot (`#create-form-slot`) move to a section header above the grid, matching go-backend's `.overview-section-heading` pattern.
#### Tablo Empty State
- **D-E01:** When no tablos exist, use `@ui.EmptyState(ui.EmptyStateProps{...})` — the Phase 13 component. Replace the current raw HTML empty state in `TablosEmptyState()`. Title: "No tablos yet". Description: "Create your first tablo to get started." Action: "New tablo" button.
#### Sidebar Footer (User/Account)
- **D-F01:** Include a sidebar footer matching go-backend's `.sidebar-organization` section. Shows: avatar circle (first letter of email, colored background) + user email + a small account link. Logout is moved from the old top-nav into this footer area.
- **D-F02:** The account/settings page doesn't exist yet, so the footer button/link only shows the email + logout button. No settings navigation for Phase 15. The `.organization-button` wraps a simple display element, not a dropdown.
- **D-F03:** Port `.sidebar-organization`, `.organization-button`, `.organization-avatar`, `.organization-name`, `.organization-meta` CSS from go-backend into `app.css`.
### Claude's Discretion
- Exact set of SVG icons for each nav item (copy from go-backend `dashboard_components.templ` — no new icon design needed)
- Whether the sidebar-projects section is a separate templ component or inline in `AppLayout` (prefer separate `SidebarProjectsSection` for testability)
- Exact color for the neutral avatar fallback (use `--color-surface-muted` or similar design token)
- Whether sidebar-footer avatar background color is derived from email hash or a fixed brand color
- Chat nav item: visual-only placeholder or links to `/` dashboard
### Deferred Ideas (OUT OF SCOPE)
- Sidebar collapse toggle (JS interaction)
- Mobile-responsive sidebar (hamburger menu, slide-in drawer)
- Tablo color picker in the create/edit form
- Tasks, Files, Settings as standalone pages
</user_constraints>
---
<phase_requirements>
## Phase Requirements
| ID | Description | Research Support |
|----|-------------|------------------|
| DASH-01 | Sidebar uses the go-backend sidebar design (brand section, nav items with icons, tablo list, user/account footer) | AppLayout + SidebarProjectsSection templ components; CSS ported from go-backend app.css lines 455743 |
| DASH-02 | Tablo list uses project-card layout with color accents, creation date, and action controls | TablosDashboard restyled with `.project-grid` + `.project-card`; CSS from app.css lines 8941046 |
| DASH-03 | Dashboard empty state uses the empty-state component | `ui.EmptyState` already implemented (Phase 13, `backend/internal/web/ui/empty_state.templ`), ready to use |
</phase_requirements>
---
## Architectural Responsibility Map
| Capability | Primary Tier | Secondary Tier | Rationale |
|------------|-------------|----------------|-----------|
| App layout shell (sidebar + main) | Backend (Go templ) | — | Server-rendered HTML; AppLayout is a templ component |
| Sidebar CSS (`.dashboard-shell`, `.sidebar-nav-shell`, etc.) | Static (CSS file) | — | `backend/internal/web/ui/app.css` serves via `/static/tailwind.css` build |
| Active nav item computation | Backend (pure Go helper) | — | `isActivePath(activePath, href)` pure function, no UI logic |
| Tablo project card grid | Backend (Go templ) | — | `TablosDashboard` restyled; CSS ported |
| Sidebar tablo list (SidebarProjectsSection) | Backend (Go templ) | — | Receives `[]sqlc.Tablo` from `AppLayout` |
| Color avatar rendering | Backend (Go templ) | — | Inline `style=` attribute with `tablo.Color` or fallback token |
| Empty state | Backend (Go templ) | — | `@ui.EmptyState(...)` already in Phase 13 |
| HTMX interactions (edit, delete) | Browser (HTMX attributes) | Backend (handler) | Attributes remain unchanged from current `TabloCard` |
---
## Standard Stack
### Core (already in backend — no new installs)
| Library | Version | Purpose | Why Standard |
|---------|---------|---------|--------------|
| templ | see go.mod | HTML templating for Go | Project standard; all templates use it |
| sqlc | see go.mod | Type-safe SQL | Project standard; `sqlc.Tablo` is the data type |
| chi | see go.mod | HTTP routing | Project standard |
| gorilla/csrf | see go.mod | CSRF token injection | Project standard; `csrf.Token(r)` in every handler |
### Supporting (CSS tokens)
| Asset | Location | Purpose | When to Use |
|-------|----------|---------|-------------|
| `backend/internal/web/ui/base.css` | Existing | Design tokens (`var(--...)`) | All new CSS must use these tokens, not raw colors |
| `go-backend/internal/web/ui/app.css` | Reference | Source CSS to port verbatim | Extract only sidebar + project-card sections |
| `backend/static/logo_dark.png` | Existing | Sidebar brand logo | Already copied in Phase 14 |
**Version verification:** No new packages needed — all required tools are already in the go.mod. [VERIFIED: codebase grep]
---
## Architecture Patterns
### System Architecture Diagram
```
HTTP GET /
|
v
TablosListHandler
|-- Queries.ListTablosByUserWithDiscussionUnread(userID) --> []ListRow
|-- Queries.ListTablosByUser(userID) --> []sqlc.Tablo (for sidebar)
v
templates.TablosDashboard(user, csrfToken, activePath="/", tablos, cardViews)
|
v
AppLayout(title, user, csrfToken, activePath, tablos)
|-- DashboardSidebar
| |-- brand section (logo_dark.png + "XTablo")
| |-- SidebarNavItems (panels/tasks/layers/planning/chat/files)
| |-- SidebarProjectsSection(tablos)
| |-- SidebarOrganizationFooter(user.Email)
v
|-- <main id="app-main-content">
{ children... }
|-- section-heading (New tablo button + #create-form-slot)
|-- #tablos-list
|-- if empty: @ui.EmptyState(...)
|-- else: for each card: @TabloProjectCard(card, csrfToken)
```
### Recommended Project Structure
```
backend/
├── templates/
│ ├── app_layout.templ # NEW — AppLayout + SidebarProjectsSection + SidebarNavItem
│ ├── app_layout_helpers.go # NEW — isActivePath, sidebarNavItemClass, sidebarNavItemID
│ ├── tablos.templ # MODIFIED — TablosDashboard, TablosEmptyState, TabloCard
│ ├── layout.templ # UNCHANGED (preserved as dead code)
│ └── ...
├── internal/web/ui/
│ ├── app.css # NEW — ported from go-backend app.css (sidebar + project-card sections only)
│ ├── base.css # UNCHANGED — design tokens used by app.css
│ └── ...
├── internal/web/
│ ├── handlers_tablos.go # MODIFIED — TablosDashboard call adds activePath + tablos params
│ ├── handlers_planning.go # MODIFIED — Planning page switches to AppLayout
│ └── ...
```
### Pattern 1: AppLayout Templ (follow AuthLayout pattern)
The AuthLayout in Phase 14 is the established pattern for a top-level HTML shell. `AppLayout` follows the same convention — package `templates`, explicit param list, `{ children... }` slot.
```go
// Source: backend/templates/auth_layout.templ (Phase 14 pattern)
// backend/templates/app_layout.templ
package templates
import (
"backend/internal/auth"
"backend/internal/db/sqlc"
)
templ AppLayout(title string, user *auth.User, csrfToken string, activePath string, tablos []sqlc.Tablo) {
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<title>{ title }</title>
<link rel="stylesheet" href="/static/tailwind.css"/>
</head>
<body>
<div class="dashboard-shell">
@DashboardSidebar(activePath, tablos, user, csrfToken)
<main id="app-main-content" class="dashboard-main flex-1 overflow-auto">
{ children... }
</main>
</div>
<script src="/static/htmx.min.js" defer></script>
<script src="/static/sortable.min.js" defer></script>
<script src="/static/discussion-sse.js" defer></script>
</body>
</html>
}
```
[VERIFIED: codebase read — `AuthLayout` signature + `Layout` script list]
### Pattern 2: Active Nav Item Helper (pure Go, not templ)
```go
// Source: go-backend/internal/web/views/home.go — sidebarNavItemClass, isActivePath
// backend/templates/app_layout_helpers.go
package templates
import "strings"
func sidebarNavItemClass(active bool) string {
if active {
return "sidebar-nav-item is-active"
}
return "sidebar-nav-item"
}
func isActivePath(activePath string, href string) bool {
return strings.TrimSpace(activePath) != "" && activePath == href
}
```
[VERIFIED: go-backend/internal/web/views/home.go lines 1737]
### Pattern 3: SidebarProjectsSection as separate templ
Per D-N04, sidebar projects render colored circle + truncated title. The go-backend uses `.sidebar-project-icon` as a circle (border-radius: 999px). The backend `sqlc.Tablo` type has `Color pgtype.Text` (nullable). The color span uses inline `style=` only when color is set.
```go
// Source: go-backend/internal/web/views/dashboard_components.templ lines 103115
// backend/templates/app_layout.templ (separate templ component)
templ SidebarProjectsSection(tablos []sqlc.Tablo) {
<div id="sidebar-projects-section" class="sidebar-projects">
<hr role="separator"/>
<div class="sidebar-section-label">Projects</div>
<ul class="sidebar-project-list">
for _, tablo := range tablos {
<li>
<a class="sidebar-project-link" href={ templ.SafeURL("/tablos/" + tablo.ID.String()) }>
<span class="sidebar-project-icon"
if tablo.Color.Valid && tablo.Color.String != "" {
style={ "background-color: " + tablo.Color.String }
}>
</span>
<span class="sidebar-project-label">{ tablo.Title }</span>
</a>
</li>
}
</ul>
</div>
}
```
[VERIFIED: go-backend templ + backend sqlc.Tablo type from handlers_tablos.go + discussion_forms.go]
### Pattern 4: TabloProjectCard restyled
The existing `TabloCard` wraps `@ui.Card(...)`. The new `TabloProjectCard` (or restyled `TabloCard`) uses the `.project-card` CSS directly. Edit/delete use `@ui.IconButton(...)` (already available, Phase 13).
Key difference from go-backend: backend uses `hx-get=/tablos/{id}/edit` (not a full-page swap) and `hx-post=/tablos/{id}/delete`**preserve existing HTMX attributes exactly**, only restyle the surrounding HTML.
```go
// Source: go-backend/internal/web/views/tablos.templ TabloGridCardWithAttrs
// Adapted for backend's HTMX interaction contract
templ TabloProjectCard(card TabloCardView, csrfToken string) {
<article id={ "tablo-" + card.Tablo.ID.String() } class="project-card">
<div class="project-card-top">
<div class="flex items-center gap-2">
@ui.IconButton(ui.IconButtonProps{
Label: "Edit tablo", Icon: "pencil",
Variant: ui.IconButtonVariantNeutral, Tone: ui.IconButtonToneGhost,
Type: "button",
Attrs: templ.Attributes{
"hx-get": "/tablos/" + card.Tablo.ID.String() + "/edit",
// ...existing HTMX attrs...
},
})
// delete icon button with existing HTMX attrs
</div>
</div>
<div class="project-card-title-row">
<span class="project-avatar"
if card.Tablo.Color.Valid && card.Tablo.Color.String != "" {
style={ "background-color: " + card.Tablo.Color.String }
}>
</span>
<h4>{ card.Tablo.Title }</h4>
</div>
<div class="project-date-row">
// creation date from card.Tablo.CreatedAt
</div>
</article>
}
```
[ASSUMED: exact HTMX attribute targets need to be verified against current TabloDeleteButtonFragment and edit handler behavior before writing the final template]
### Pattern 5: Handler Changes — TablosDashboard Call Sites
Currently, `TablosDashboard(user, csrfToken, tabloCards)` is called from `TablosListHandler`. Per D-L01, the new signature is `TablosDashboard(user, csrfToken, activePath, tablos, cardViews)` where `tablos []sqlc.Tablo` feeds the sidebar and `cardViews []TabloCardView` feeds the grid.
The tablos list is **already fetched** by `TablosListHandler` — it calls `ListTablosByUserWithDiscussionUnread` which returns `[]ListRow`. We need a second lightweight query `ListTablosByUser(userID)``[]sqlc.Tablo` for the sidebar (or derive from existing rows).
```go
// Source: backend/internal/db/sqlc/tablos.sql.go line 88100
// ListTablosByUser already exists:
// func (q *Queries) ListTablosByUser(ctx context.Context, userID uuid.UUID) ([]Tablo, error)
```
[VERIFIED: grep in backend/internal/db/sqlc/tablos.sql.go]
**Simpler option:** Derive `[]sqlc.Tablo` from the already-fetched unread rows without a second DB query:
```go
// In TabloCardsFromUnreadRows, each row already contains all Tablo fields.
// We can produce []sqlc.Tablo by extracting the Tablo field from each TabloCardView.
tablos := make([]sqlc.Tablo, 0, len(cardViews))
for _, cv := range cardViews {
tablos = append(tablos, cv.Tablo)
}
```
This avoids an extra query. [ASSUMED: confirm this approach in planning since it avoids a new DB call but relies on already-fetched data order]
### Pattern 6: Planning and Other Layout Callers
Currently `planning.templ` calls `@Layout(...)`. Per D-L01, it must switch to `@AppLayout(...)`. Planning handler (`PlanningPageHandler`) does not currently fetch tablos. It will need to fetch `[]sqlc.Tablo` for the sidebar. The `PlanningDeps` struct already has `Queries *sqlc.Queries`, so `deps.Queries.ListTablosByUser(ctx, user.ID)` is available.
Pages that call `@Layout(...)` today and need to switch:
- `tablos.templ`: `TablosDashboard`, `TabloDetailPage`, `TabloNotFoundPage`
- `planning.templ`: `PlanningPage`
- `account_providers.templ`: `AccountProvidersPage` (uses `@Layout`)
[VERIFIED: grep of Layout( in backend/templates/*.templ]
### Anti-Patterns to Avoid
- **Re-using `.sidebar-project-icon` as a colored SVG wrapper:** In go-backend it wraps an SVG icon. In this phase, per D-C03, it should wrap a plain colored `<span>` (no SVG) — the color is the only visual indicator.
- **Nesting OOB elements inside AppLayout:** OOB HTMX fragments (`hx-swap-oob`) must be top-level siblings, not nested under AppLayout. This pattern is already established in `TabloCardWithOOBFormClear`.
- **Hard-coded colors in app.css:** All color values must reference `var(--...)` design tokens from `base.css`. The go-backend app.css already does this — port verbatim.
- **Two queries where one suffices:** `ListTablosByUserWithDiscussionUnread` already returns all tablo fields; derive `[]sqlc.Tablo` from it rather than issuing a second query in `TablosListHandler`.
---
## Don't Hand-Roll
| Problem | Don't Build | Use Instead | Why |
|---------|-------------|-------------|-----|
| Empty state component | Raw HTML `<div>` with inline styles | `@ui.EmptyState(ui.EmptyStateProps{...})` | Phase 13 component; `.ui-empty-state` CSS already loaded |
| Icon buttons (edit/delete on card) | Raw `<button>` with SVG | `@ui.IconButton(ui.IconButtonProps{...})` | Phase 13 component; `.ui-icon-button-*` CSS already loaded |
| CSRF hidden field | `<input type="hidden">` | `@ui.CSRFField(csrfToken)` | Existing helper; gorilla/csrf invariant |
| Design tokens | Inline hex color strings | `var(--color-*)` tokens from base.css | Token system already in place; ensures theme coherence |
---
## Common Pitfalls
### Pitfall 1: `tailwind.css` serves the compiled CSS bundle — app.css must be imported into it
**What goes wrong:** Creating `backend/internal/web/ui/app.css` but forgetting to `@import` it into `backend/internal/web/ui/tailwind.css` (or equivalent entry point). The file exists but its CSS never reaches the browser.
**Why it happens:** The backend serves `/static/tailwind.css` as the CSS entry point. All component CSS files (`auth.css`, `button.css`, etc.) are imported into this bundle. A new `app.css` must be added the same way.
**How to avoid:** Check how `auth.css` is imported (it was added in Phase 14) and add `app.css` the same way.
**Warning signs:** Sidebar renders with no styles in the browser despite the file existing.
[VERIFIED: `backend/internal/web/ui/auth.css` exists; Phase 14 added it — same pattern needed for `app.css`]
### Pitfall 2: `Color pgtype.Text` null-safety in templates
**What goes wrong:** Accessing `tablo.Color.String` directly without checking `tablo.Color.Valid` causes the template to emit an empty or zero-value string when Color is NULL in the database.
**Why it happens:** pgtype.Text is a nullable wrapper; `.Valid` must be checked before using `.String`.
**How to avoid:** Always guard: `if tablo.Color.Valid && tablo.Color.String != ""` before using `tablo.Color.String` in a `style=` attribute.
[VERIFIED: existing `tabloCardBody` in tablos.templ already demonstrates this pattern at lines 8590]
### Pitfall 3: HTMX target for edit action changed between go-backend and backend
**What goes wrong:** Copying go-backend's `EditTabloButton` HTMX attrs verbatim. Go-backend uses `hx-target="#app-main-content"` (full-page swap). The backend uses in-place zone swaps (`hx-target="closest .tablo-delete-zone"`).
**Why it happens:** The two codebases have different interaction contracts for edit/delete.
**How to avoid:** Per D-C04, preserve the existing HTMX attributes from the current `TabloDeleteButtonFragment` and inline edit flow. Only restyle the surrounding HTML structure — do not change the `hx-*` attributes.
[VERIFIED: current tablos.templ + go-backend tablos.templ comparison]
### Pitfall 4: `AppLayout` children slot — `TablosListHandler` renders `TablosDashboard` which calls `AppLayout`
**What goes wrong:** `TablosListHandler` currently calls `templates.TablosDashboard(user, csrf, cards).Render(...)`. After the change, `TablosDashboard` renders inside `AppLayout` via `{ children... }`. The handler call must pass `activePath` and `tablos` to `TablosDashboard`, which in turn passes them to `AppLayout`.
**How to avoid:** Update the `TablosDashboard` templ signature to accept `activePath string` and `tablos []sqlc.Tablo` and forward them to `AppLayout`. Update the handler call accordingly.
### Pitfall 5: `templ generate` must run after every `.templ` file change
**What goes wrong:** Editing `.templ` files without re-running `templ generate` means the `*_templ.go` generated files are stale. Go compilation fails or uses old logic.
**How to avoid:** The project uses `air` for hot-reload which calls `templ generate` automatically in dev. For Wave 0 / test tasks, run `go generate ./...` or `templ generate` explicitly.
[VERIFIED: `go-backend/justfile` and project convention]
### Pitfall 6: CreatedAt formatting — `pgtype.Timestamptz` not `time.Time`
**What goes wrong:** Calling `.Format(...)` directly on `card.Tablo.CreatedAt` — it is `pgtype.Timestamptz`, not `time.Time`. The Time value is at `.Time` field.
**How to avoid:** Use `card.Tablo.CreatedAt.Time.Format("Jan 2, 2006")` or a helper function (follow go-backend's `dates.FormatFrenchDate` pattern, but using English format per backend convention).
[VERIFIED: sqlc.Tablo type — CreatedAt is pgtype.Timestamptz; existing files use `.Time` accessor pattern]
---
## Code Examples
### CSS sections to port from go-backend app.css
**Sidebar CSS block** (go-backend app.css lines 455743):
- `.dashboard-shell` — grid layout (line 455)
- `.dashboard-sidebar` — sidebar column (line 463)
- `.sidebar-nav-shell` — sticky nav shell (line 467)
- `.sidebar-brand`, `.sidebar-brand-link`, `.sidebar-brand-logo`, `.sidebar-brand-title` (lines 481509)
- `.sidebar-collapse-button` (lines 511527) — include but will be non-functional (no JS in Phase 15)
- `.sidebar-primary`, `.sidebar-list`, `.sidebar-divider` (lines 533558)
- `.sidebar-nav-item`, `.sidebar-nav-item:hover`, `.sidebar-nav-item.is-active` (lines 560578)
- `.sidebar-nav-link`, `.sidebar-nav-link-inner`, `.sidebar-nav-icon`, `.sidebar-nav-label` (lines 580605)
- `.sidebar-projects`, `.sidebar-section-label`, `.sidebar-project-list` (lines 607628)
- `.sidebar-project-link`, `.sidebar-project-icon`, `.sidebar-project-label` (lines 630668)
- `.sidebar-footer-links` (lines 670673)
- `.sidebar-organization` + all `.organization-*` classes (lines 675732)
- `.dashboard-main` (lines 734741)
**Project card CSS block** (go-backend app.css lines 8931046):
- `.overview-section`, `.overview-section-heading` (lines 875884)
- `.project-grid` (lines 894899)
- `.project-card`, `.project-card-top` (lines 901914)
- `.project-card-top .borderless-icon-button` overrides (lines 945963) — NOTE: backend uses `.ui-icon-button-*` classes (Phase 13), not `.borderless-icon-button`; these overrides may need adaptation
- `.project-card-title-row`, `.project-avatar` (lines 9861005)
- `.project-date-row` (lines 10391046)
[VERIFIED: go-backend/internal/web/ui/app.css line counts verified by reading the file]
### Mapping `ui.IconButton` to project-card context
The go-backend uses `.borderless-icon-button` as a class modifier. The backend's `ui.IconButton` generates classes like `.ui-icon-button-ghost.ui-icon-button-neutral`. The CSS overrides in go-backend (lines 945963) target `.borderless-icon-button` — these must be adapted to target the Phase 13 class names instead.
```css
/* In app.css — adapt from go-backend lines 945-963 */
.project-card-top .ui-icon-button {
padding: 0;
}
.project-card-top .ui-icon-button-ghost.ui-icon-button-neutral:hover {
color: var(--color-surface-muted-inverse);
}
.project-card-top .ui-icon-button-ghost.ui-icon-button-danger:hover {
color: var(--color-status-danger-icon-hover);
}
```
[ASSUMED: exact Phase 13 `IconButtonClass()` output needs to be confirmed against `backend/internal/web/ui/variants.go` before writing final CSS]
---
## State of the Art
| Old Approach | Current Approach | When Changed | Impact |
|--------------|------------------|--------------|--------|
| Top-nav `Layout` shell | Sidebar `AppLayout` shell | Phase 15 (this phase) | All authenticated pages must use `AppLayout` |
| Raw HTML empty state | `@ui.EmptyState(...)` component | Phase 13 (already done) | TablosEmptyState must be replaced |
| Raw icon buttons | `@ui.IconButton(...)` | Phase 13 (already done) | TabloCard delete button already uses ui.Button; switch to IconButton |
---
## Assumptions Log
| # | Claim | Section | Risk if Wrong |
|---|-------|---------|---------------|
| A1 | Derive `[]sqlc.Tablo` from already-fetched `TabloCardView.Tablo` fields rather than a second DB query | Code Examples / Pattern 5 | Low — fallback is to call `ListTablosByUser` separately; minor perf difference only |
| A2 | Phase 13 `IconButtonClass()` generates `.ui-icon-button-ghost` and `.ui-icon-button-neutral` / `.ui-icon-button-danger` as class names | Code Examples — CSS mapping | Medium — if class names differ, project-card-top icon hover styles won't work; fix by reading `variants.go` |
| A3 | HTMX edit interaction for TabloCard uses a modal or page-level swap (not inline zone swap) | Pattern 4 | Medium — the edit path in go-backend uses full-page swap; the backend edit handler may differ; check `handlers_tablos.go` TablosEditHandler |
---
## Open Questions
1. **Does TabloDetailPage also switch to AppLayout in Phase 15?**
- What we know: CONTEXT.md says "All authenticated dashboard pages use AppLayout" (D-L01). `TabloDetailPage` is a dashboard page.
- What's unclear: CONTEXT.md phase boundary says "Does NOT include: tablo detail restyling (Phase 16)". This is ambiguous — does the layout shell switch happen in Phase 15 or Phase 16?
- Recommendation: Switch `TabloDetailPage` to `AppLayout` in Phase 15 (layout shell only, no restyling of the detail content). This avoids a regression where the sidebar disappears on the detail page after Phase 15 goes live.
2. **What is the exact HTMX edit interaction for TabloCard in the current backend?**
- What we know: `TabloDeleteButtonFragment` uses inline zone swap. Current `tabloCardBody` has only a "View" link, no edit button.
- What's unclear: Per D-C04, edit uses `hx-get=/tablos/{id}/edit`. Does this handler exist? What does it return?
- Recommendation: Check `handlers_tablos.go` for a `TablosEditHandler` before implementing D-C04. If it's a full-page edit form (not inline), the HTMX target may be `#app-main-content`.
3. **How does CSS enter the `/static/tailwind.css` bundle?**
- What we know: `auth.css` was added in Phase 14 and works. The bundle entry point is somewhere in `backend/internal/web/ui/`.
- What's unclear: Is there a CSS manifest file or is it a single CSS file with `@import` directives?
- Recommendation: Check how `auth.css` was wired in Phase 14 and replicate for `app.css`.
---
## Environment Availability
Step 2.6: SKIPPED — phase is purely code/template/CSS changes; no new external tools, services, or CLIs required. All Go tooling (`templ`, `sqlc`, `go build`) already confirmed available in previous phases.
---
## Validation Architecture
### Test Framework
| Property | Value |
|----------|-------|
| Framework | Go `testing` + `net/http/httptest` (existing) |
| Config file | none — standard Go test runner |
| Quick run command | `cd backend && go test ./internal/web/... -run TestTablos -v` |
| Full suite command | `cd backend && go test ./...` |
### Phase Requirements → Test Map
| Req ID | Behavior | Test Type | Automated Command | File Exists? |
|--------|----------|-----------|-------------------|-------------|
| DASH-01 | Sidebar renders brand, nav items, tablo list, footer in GET / response | integration | `go test ./internal/web/... -run TestTablosDashboard_Sidebar` | ❌ Wave 0 |
| DASH-02 | GET / renders `.project-card` elements for each tablo | integration | `go test ./internal/web/... -run TestTablosDashboard_ProjectCards` | ❌ Wave 0 |
| DASH-03 | GET / with zero tablos renders `.ui-empty-state` | integration | `go test ./internal/web/... -run TestTablosDashboard_EmptyState` | ❌ Wave 0 |
| (regression) | Existing TABLO-01..06 handler tests pass unchanged | integration | `go test ./internal/web/... -run TestTablos` | ✅ `handlers_tablos_test.go` |
### Sampling Rate
- **Per task commit:** `cd backend && go test ./internal/web/... -run TestTablos -count=1`
- **Per wave merge:** `cd backend && go test ./... -count=1`
- **Phase gate:** Full suite green before `/gsd-verify-work`
### Wave 0 Gaps
- [ ] `backend/internal/web/handlers_tablos_test.go` — add `TestTablosDashboard_Sidebar`, `TestTablosDashboard_ProjectCards`, `TestTablosDashboard_EmptyState` test functions
- [ ] Tests should assert HTML structure using `strings.Contains` on response body (matching existing pattern in `handlers_auth_test.go`)
---
## Security Domain
### Applicable ASVS Categories
| ASVS Category | Applies | Standard Control |
|---------------|---------|-----------------|
| V2 Authentication | No | Phase 15 is UI-only; auth unchanged |
| V3 Session Management | No | No new session logic |
| V4 Access Control | No | No new routes or access rules |
| V5 Input Validation | No | No new form inputs |
| V6 Cryptography | No | No new crypto |
No new security surface in Phase 15 — it is a pure visual restyle of existing authenticated routes. All existing CSRF, session, and auth middleware remain unchanged. [VERIFIED: phase boundary confirms no new routes or form handlers]
---
## Sources
### Primary (HIGH confidence)
- `go-backend/internal/web/views/dashboard_components.templ` — complete sidebar + project-card templ structure (read in this session)
- `go-backend/internal/web/ui/app.css` — complete CSS source for all sidebar + project-card styles (read in this session)
- `go-backend/internal/web/views/home.go` — helper functions: `sidebarNavItemClass`, `isActivePath`, `sidebarNavItemID`, `sidebarPrimaryNavItems`, `sidebarProjectItems` (read in this session)
- `backend/templates/tablos.templ` — current template being replaced (read in this session)
- `backend/templates/layout.templ` — current layout being superseded (read in this session)
- `backend/templates/auth_layout.templ` — Phase 14 pattern for AppLayout to follow (read in this session)
- `backend/templates/discussion_forms.go``TabloCardView` type and `TabloCardsFromUnreadRows` (read in this session)
- `backend/internal/web/ui/empty_state.templ``ui.EmptyState` component signature (read in this session)
- `backend/internal/web/ui/icon_button.templ``ui.IconButton` component signature (read in this session)
- `backend/internal/web/handlers_tablos.go` — current handler call sites (read in this session)
- `backend/internal/db/sqlc/tablos.sql.go``ListTablosByUser` query exists (verified via grep)
### Secondary (MEDIUM confidence)
- `backend/internal/web/handlers_planning.go` — planning handler structure; will need tablos fetch added (read in this session)
---
## Metadata
**Confidence breakdown:**
- Standard stack: HIGH — no new packages; all dependencies already in project
- Architecture: HIGH — canonical reference (go-backend) read directly; exact CSS lines identified
- Pitfalls: HIGH — verified against actual codebase code patterns
- Handler changes: MEDIUM — edit interaction HTMX target for TabloCard open question (Open Question #2)
**Research date:** 2026-05-16
**Valid until:** 2026-06-16 (stable CSS-only domain; unlikely to change)