docs(15): research phase domain
This commit is contained in:
parent
65c17561ae
commit
8422d82fc8
1 changed files with 592 additions and 0 deletions
592
.planning/phases/15-dashboard-tablos/15-RESEARCH.md
Normal file
592
.planning/phases/15-dashboard-tablos/15-RESEARCH.md
Normal 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 455–743 cover all sidebar CSS; lines 894–1046 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 455–743 |
|
||||
| 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 894–1046 |
|
||||
| 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 17–37]
|
||||
|
||||
### 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 103–115
|
||||
// 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 88–100
|
||||
// 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 85–90]
|
||||
|
||||
### 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 455–743):
|
||||
- `.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 481–509)
|
||||
- `.sidebar-collapse-button` (lines 511–527) — include but will be non-functional (no JS in Phase 15)
|
||||
- `.sidebar-primary`, `.sidebar-list`, `.sidebar-divider` (lines 533–558)
|
||||
- `.sidebar-nav-item`, `.sidebar-nav-item:hover`, `.sidebar-nav-item.is-active` (lines 560–578)
|
||||
- `.sidebar-nav-link`, `.sidebar-nav-link-inner`, `.sidebar-nav-icon`, `.sidebar-nav-label` (lines 580–605)
|
||||
- `.sidebar-projects`, `.sidebar-section-label`, `.sidebar-project-list` (lines 607–628)
|
||||
- `.sidebar-project-link`, `.sidebar-project-icon`, `.sidebar-project-label` (lines 630–668)
|
||||
- `.sidebar-footer-links` (lines 670–673)
|
||||
- `.sidebar-organization` + all `.organization-*` classes (lines 675–732)
|
||||
- `.dashboard-main` (lines 734–741)
|
||||
|
||||
**Project card CSS block** (go-backend app.css lines 893–1046):
|
||||
- `.overview-section`, `.overview-section-heading` (lines 875–884)
|
||||
- `.project-grid` (lines 894–899)
|
||||
- `.project-card`, `.project-card-top` (lines 901–914)
|
||||
- `.project-card-top .borderless-icon-button` overrides (lines 945–963) — 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 986–1005)
|
||||
- `.project-date-row` (lines 1039–1046)
|
||||
|
||||
[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 945–963) 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)
|
||||
Loading…
Reference in a new issue