# Go Backend Tablos Index CRUD Implementation Plan

> **For agentic workers:** REQUIRED: Use superpowers:subagent-driven-development (if subagents available) or superpowers:executing-plans to implement this plan. Steps use checkbox (`- [ ]`) syntax for tracking.

**Goal:** Build the first real `/tablos` vertical slice in the Go rewrite with server-rendered list, modal create, soft delete, and functional URL-backed grid/list, search, and status filters.

**Architecture:** Extend the current Go app from auth-only pages into a small app domain for `tablos`. Keep data access in Postgres via sqlc and the repository, keep request parsing and ownership checks in handlers, and keep HTMX fragment boundaries explicit in `templ` views so full-page and partial-page responses share the same rendering units.

**Tech Stack:** Go, chi, templ, HTMX, PostgreSQL, pgx, sqlc, Go standard `net/http` testing

---

## File Structure

**Existing files to modify**

- `go-backend/internal/db/schema.sql`
  - Add the `public.tablos` table and any required index.
- `go-backend/internal/db/queries.sql`
  - Add list/create/delete SQL queries for tablos.
- `go-backend/internal/db/repository.go`
  - Extend the repository with `tablos` methods backed by sqlc. Take the time to split this file and create a new repository package that contains the tablos.go and users.go
- `go-backend/router.go`
  - Register `POST /tablos` and `DELETE /tablos/{id}`.
- `go-backend/internal/web/handlers/auth.go`
  - Update `GetTablosPage()` to use real data instead of placeholder content.
- `go-backend/internal/web/handlers/in_memory_auth_repository.go`
  - Extend the in-memory test repository with tablo storage and filter/delete behavior.
- `go-backend/internal/web/views/pages.templ`
  - Wire the `/tablos` page to real tablos content if needed by the final component split.
  - Wire the `/` page to real tablos content
- `go-backend/internal/web/views/home.go`
  - Replace hard-coded overview/tablo placeholder helpers with real tablos view models where needed.
- `go-backend/router_test.go`
  - Add full-router tests for `/tablos` page load and HTMX fragment behavior.

**New files to create**

- `go-backend/internal/web/handlers/tablos.go`
  - Parse query state, validate create input, validate ownership for delete, and render page/modal fragments.
- `go-backend/internal/web/handlers/tablos_test.go`
  - Focused handler tests for `/tablos` filtering, create, and delete behavior.
- `go-backend/internal/web/views/tablos.templ`
  - Main `/tablos` page content, grid/list variants, filter bar, and modal fragments.
- `go-backend/internal/web/views/tablos_view.go`
  - View models, formatting helpers, status labels, safe defaults, and query-state helpers for the tablos page.

**Generated files expected to change**

- `go-backend/internal/db/sqlc/*.go`
  - Regenerated by `just generate` after schema/query updates.
- `go-backend/internal/web/views/*_templ.go`
  - Regenerated by `just generate` after `templ` changes.

**Test and verification commands**

- `cd go-backend && go test ./internal/web/handlers -run Tablos`
- `cd go-backend && go test ./...`
- `cd go-backend && just generate`
- `cd go-backend && just build`

## Chunk 1: Data Model And Repository

### Task 1: Add the failing repository tests for tablos storage behavior

**Files:**
- Modify: `go-backend/internal/web/handlers/in_memory_auth_repository.go`
- Create: `go-backend/internal/web/handlers/tablos_test.go`

- [ ] **Step 1: Write the failing tests for create/list/delete semantics**

Add handler/repository-level tests that prove the repository contract before production code exists:

```go
func TestInMemoryTablosListExcludesSoftDeletedRows(t *testing.T) {
	repo := NewInMemoryAuthRepository()
	user, _ := repo.GetAuthUserByEmail(context.Background(), "demo@xtablo.com")

	first, err := repo.CreateTablo(context.Background(), CreateTabloInput{
		OwnerID: user.ID,
		Name:    "Visible",
		Status:  TabloStatusTodo,
	})
	if err != nil {
		t.Fatal(err)
	}

	_, err = repo.CreateTablo(context.Background(), CreateTabloInput{
		OwnerID: user.ID,
		Name:    "Hidden",
		Status:  TabloStatusTodo,
	})
	if err != nil {
		t.Fatal(err)
	}

	if err := repo.SoftDeleteTablo(context.Background(), first.ID, user.ID); err != nil {
		t.Fatal(err)
	}

	tablos, err := repo.ListTablos(context.Background(), ListTablosInput{OwnerID: user.ID})
	if err != nil {
		t.Fatal(err)
	}

	if len(tablos) != 1 {
		t.Fatalf("expected 1 visible tablo, got %d", len(tablos))
	}
}
```

- [ ] **Step 2: Run the focused tests to verify they fail**

Run: `cd go-backend && go test ./internal/web/handlers -run 'Tablos|InMemoryTablos'`

Expected: FAIL with missing tablo types or repository methods.

- [ ] **Step 3: Add the minimal in-memory domain types and storage**

Add the minimum runtime types used by both handlers and repository implementations:

```go
type TabloStatus string

const (
	TabloStatusTodo       TabloStatus = "todo"
	TabloStatusInProgress TabloStatus = "in_progress"
	TabloStatusDone       TabloStatus = "done"
)

type TabloRecord struct {
	ID        uuid.UUID
	OwnerID   uuid.UUID
	Name      string
	Status    TabloStatus
	CreatedAt time.Time
	UpdatedAt time.Time
	DeletedAt *time.Time
}
```

Extend the in-memory repo with:

- `tablos map[uuid.UUID]TabloRecord`
- `CreateTablo`
- `ListTablos`
- `SoftDeleteTablo`

- [ ] **Step 4: Re-run the focused tests to verify they pass**

Run: `cd go-backend && go test ./internal/web/handlers -run 'Tablos|InMemoryTablos'`

Expected: PASS for the new in-memory storage behavior tests.

- [ ] **Step 5: Commit**

```bash
git add go-backend/internal/web/handlers/in_memory_auth_repository.go go-backend/internal/web/handlers/tablos_test.go
git commit -m "feat: add in-memory tablo repository support"
```

### Task 2: Add the SQL schema and query contract through tests

**Files:**
- Modify: `go-backend/internal/db/schema.sql`
- Modify: `go-backend/internal/db/queries.sql`
- Modify: `go-backend/internal/db/repository.go`
- Generated: `go-backend/internal/db/sqlc/*`

- [ ] **Step 1: Write a failing repository integration-shaped test for query semantics**

Add a focused repository test near the handler test seam if there is no separate db test package yet. The test should document the needed query contract:

```go
func TestListTablosInputSupportsSearchAndStatus(t *testing.T) {
	input := ListTablosInput{
		OwnerID: ownerID,
		Query:   "hell",
		Status:  TabloStatusTodo,
	}

	_ = input
	// This test initially fails because the production repository contract does not exist yet.
}
```

The purpose is to drive the API shape before editing SQL.

- [ ] **Step 2: Run the focused tests to verify they fail**

Run: `cd go-backend && go test ./internal/web/handlers -run 'Tablos|InMemoryTablos'`

Expected: FAIL due to missing repository input types or methods.

- [ ] **Step 3: Add the SQL schema and queries**

Update `schema.sql` with:

```sql
CREATE TABLE IF NOT EXISTS public.tablos (
  id uuid PRIMARY KEY,
  owner_id uuid NOT NULL REFERENCES public.users(id) ON DELETE CASCADE,
  name text NOT NULL,
  status text NOT NULL,
  created_at timestamptz NOT NULL DEFAULT now(),
  updated_at timestamptz NOT NULL DEFAULT now(),
  deleted_at timestamptz NULL
);

CREATE INDEX IF NOT EXISTS tablos_owner_created_idx
  ON public.tablos (owner_id, created_at DESC)
  WHERE deleted_at IS NULL;
```

Add sqlc queries:

```sql
-- name: CreateTablo :one
INSERT INTO public.tablos (
  id, owner_id, name, status, created_at, updated_at
) VALUES (
  $1, $2, $3, $4, now(), now()
)
RETURNING id, owner_id, name, status, created_at, updated_at, deleted_at;

-- name: ListTablos :many
SELECT id, owner_id, name, status, created_at, updated_at, deleted_at
FROM public.tablos
WHERE owner_id = sqlc.arg(owner_id)
  AND deleted_at IS NULL
  AND (
    sqlc.narg(status)::text IS NULL OR status = sqlc.narg(status)::text
  )
  AND (
    sqlc.narg(query)::text IS NULL OR name ILIKE '%' || sqlc.narg(query)::text || '%'
  )
ORDER BY created_at DESC;

-- name: SoftDeleteTablo :execrows
UPDATE public.tablos
SET deleted_at = now(), updated_at = now()
WHERE id = $1 AND owner_id = $2 AND deleted_at IS NULL;
```

- [ ] **Step 4: Regenerate sqlc and templ artifacts**

Run: `cd go-backend && just generate`

Expected: PASS and regenerated `internal/db/sqlc` plus any `templ` outputs with no generation errors.

- [ ] **Step 5: Implement the Postgres repository methods**

Extend `repository.go` with the real methods:

```go
type ListTablosInput struct {
	OwnerID uuid.UUID
	Query   string
	Status  *TabloStatus
}

type CreateTabloInput struct {
	OwnerID uuid.UUID
	Name    string
	Status  TabloStatus
}
```

Implement:

- `CreateTablo(ctx, input)`
- `ListTablos(ctx, input)`
- `SoftDeleteTablo(ctx, tabloID, ownerID)`

Normalize empty search strings before passing them to sqlc. Return a domain-level not-found/unauthorized error when `SoftDeleteTablo` affects zero rows.

- [ ] **Step 6: Run the focused tests to verify they pass**

Run: `cd go-backend && go test ./internal/web/handlers -run 'Tablos|InMemoryTablos'`

Expected: PASS with the new contract compiling and the in-memory implementation still satisfying it.

- [ ] **Step 7: Commit**

```bash
git add go-backend/internal/db/schema.sql go-backend/internal/db/queries.sql go-backend/internal/db/repository.go go-backend/internal/db/sqlc go-backend/internal/web/handlers/in_memory_auth_repository.go go-backend/internal/web/handlers/tablos_test.go
git commit -m "feat: add tablo persistence contract"
```

## Chunk 2: Handler Routing And URL State

### Task 3: Add failing handler tests for `/tablos` query state, create, and delete

**Files:**
- Create: `go-backend/internal/web/handlers/tablos.go`
- Modify: `go-backend/internal/web/handlers/tablos_test.go`
- Modify: `go-backend/router.go`
- Modify: `go-backend/router_test.go`

- [ ] **Step 1: Write failing handler tests for GET/POST/DELETE**

Add tests that document the handler contract:

```go
func TestGetTablosPageDefaultsToGridAndAllStatus(t *testing.T) {}
func TestGetTablosPageHonorsSearchAndStatus(t *testing.T) {}
func TestPostTablosCreatesTodoTablo(t *testing.T) {}
func TestPostTablosWithEmptyNameReturns422(t *testing.T) {}
func TestDeleteTabloSoftDeletesOwnedRow(t *testing.T) {}
func TestDeleteTabloRejectsDifferentOwner(t *testing.T) {}
```

Also extend `router_test.go` with an end-to-end page assertion:

```go
func TestTablosPageRendersRealProjectsHeading(t *testing.T) {}
```

- [ ] **Step 2: Run the focused tests to verify they fail**

Run: `cd go-backend && go test ./internal/web/handlers -run Tablos`

Expected: FAIL with missing handler methods and route wiring.

- [ ] **Step 3: Implement query parsing and safe defaults**

Create `tablos.go` and add request parsing helpers:

```go
type TablosQueryState struct {
	View   string
	Query  string
	Status string
}

func parseTablosQueryState(r *http.Request) TablosQueryState {
	q := strings.TrimSpace(r.URL.Query().Get("q"))
	view := r.URL.Query().Get("view")
	status := r.URL.Query().Get("status")

	if view != "list" {
		view = "grid"
	}

	switch status {
	case "todo", "in_progress", "done":
	default:
		status = "all"
	}

	return TablosQueryState{
		View:   view,
		Query:  q,
		Status: status,
	}
}
```

- [ ] **Step 4: Implement the minimal handler methods**

Add handler methods:

- `GetTablosPage()`
- `PostTablos()`
- `DeleteTablo()`

Behavior:

- `GET /tablos` loads current user, parses query state, lists tablos, renders full page or HTMX fragment
- `POST /tablos` trims `name`, rejects empty names with `422`, creates with `status=todo`
- `DELETE /tablos/{id}` verifies ownership through the repository delete contract and preserves query state

- [ ] **Step 5: Wire the routes**

Update `router.go`:

```go
mux.Get("/tablos", authHandler.GetTablosPage())
mux.Post("/tablos", authHandler.PostTablos())
mux.Delete("/tablos/{tabloID}", authHandler.DeleteTablo())
```

- [ ] **Step 6: Re-run focused tests to verify they pass**

Run: `cd go-backend && go test ./internal/web/handlers -run Tablos`

Expected: PASS for handler-specific tests.

- [ ] **Step 7: Commit**

```bash
git add go-backend/internal/web/handlers/tablos.go go-backend/internal/web/handlers/tablos_test.go go-backend/router.go go-backend/router_test.go
git commit -m "feat: add tablo handlers and routes"
```

## Chunk 3: Templ Views, Modal, And Functional Controls

### Task 4: Add failing rendering tests for the `/tablos` page contract

**Files:**
- Create: `go-backend/internal/web/views/tablos.templ`
- Create: `go-backend/internal/web/views/tablos_view.go`
- Modify: `go-backend/internal/web/views/pages.templ`
- Modify: `go-backend/internal/web/views/home.go`
- Modify: `go-backend/internal/web/views/icons.templ`
- Modify: `go-backend/router_test.go`

- [ ] **Step 1: Write failing page-render tests for the agreed UI contract**

Add assertions for:

- `Mes Projets`
- `Nouveau projet`
- `Vue en grille`
- `Vue en liste`
- `Rechercher...`
- `Tous`, `Pas commencé`, `En cours`, `Terminé`
- grid markup in default mode
- list markup in `?view=list`
- modal markup or modal target container

Example:

```go
if !strings.Contains(body, "Mes Projets") {
	t.Fatalf("expected tablos page heading")
}
if !strings.Contains(body, "Nouveau projet") {
	t.Fatalf("expected create trigger")
}
```

- [ ] **Step 2: Run the router tests to verify they fail**

Run: `cd go-backend && go test ./... -run 'TablosPage|TablosHTMX'`

Expected: FAIL because the placeholder page still renders.

- [ ] **Step 3: Add view models and formatting helpers**

Create `tablos_view.go` with focused helpers:

- `TabloCardView`
- `TablosPageViewModel`
- French status label mapping:
  - `todo -> À faire`
  - `in_progress -> En cours`
  - `done -> Terminé`
- date formatting for French-style labels
- progress mapping:
  - `todo -> 0`
  - `in_progress -> 50`
  - `done -> 100`

- [ ] **Step 4: Add the new templ components**

Create `tablos.templ` with:

- `TablosMainContent(vm TablosPageViewModel)`
- `TablosToolbar(vm TablosPageViewModel)`
- `TablosGrid(vm TablosPageViewModel)`
- `TablosList(vm TablosPageViewModel)`
- `TabloCard(item TabloCardView, state TablosQueryState)`
- `CreateTabloModal(vm TablosPageViewModel)`

The top-level content should follow the user-provided HTML structure closely:

```html
<main class="flex-1 overflow-auto">
  <div class="px-4 pt-8 pb-6">
    ...
  </div>
</main>
```

Use HTMX attributes for:

- view toggle `hx-get`
- search `hx-get` with trigger debounce
- status pill filtering `hx-get`
- modal form `hx-post="/tablos"`
- delete buttons `hx-delete="/tablos/{id}"`

- [ ] **Step 5: Add any missing icons or class helpers**

Extend `icons.templ` only if the provided `/tablos` structure needs icons not already present:

- `plus`
- `grid3x3`
- `list`
- `search`
- `filter`

Keep icons centralized rather than inlining large SVGs repeatedly.

- [ ] **Step 6: Regenerate templ artifacts**

Run: `cd go-backend && just generate`

Expected: PASS with new `*_templ.go` output and no templ generation errors.

- [ ] **Step 7: Re-run the page tests to verify they pass**

Run: `cd go-backend && go test ./... -run 'TablosPage|TablosHTMX'`

Expected: PASS with the real `/tablos` page structure rendered.

- [ ] **Step 8: Commit**

```bash
git add go-backend/internal/web/views/tablos.templ go-backend/internal/web/views/tablos_view.go go-backend/internal/web/views/pages.templ go-backend/internal/web/views/home.go go-backend/internal/web/views/icons.templ go-backend/internal/web/views/*_templ.go go-backend/router_test.go
git commit -m "feat: render real tablos page"
```

## Chunk 4: Modal Validation, Delete UX, And Full Verification

### Task 5: Tighten validation and modal error behavior with failing tests first

**Files:**
- Modify: `go-backend/internal/web/handlers/tablos.go`
- Modify: `go-backend/internal/web/handlers/tablos_test.go`
- Modify: `go-backend/internal/web/views/tablos.templ`

- [ ] **Step 1: Write the failing tests for modal error and state preservation**

Add tests that verify:

- empty `name` returns `422`
- returned body still contains the modal and inline error
- create success preserves current `view`, `q`, and `status`
- delete success preserves current `view`, `q`, and `status`

Example:

```go
if rec.Code != http.StatusUnprocessableEntity {
	t.Fatalf("expected 422, got %d", rec.Code)
}
if !strings.Contains(rec.Body.String(), "Le nom du projet est requis") {
	t.Fatalf("expected inline validation message")
}
```

- [ ] **Step 2: Run the focused tests to verify they fail**

Run: `cd go-backend && go test ./internal/web/handlers -run 'PostTablos|DeleteTablo'`

Expected: FAIL until modal error and state-preservation behavior is implemented.

- [ ] **Step 3: Implement inline validation and state preservation**

Update `PostTablos()` and `DeleteTablo()` to:

- read current query state from request values
- reuse the same state when rendering success fragments
- return modal fragment with inline error text on `422`
- include confirm copy on delete buttons

Keep the logic minimal; do not add a client-side state store.

- [ ] **Step 4: Re-run the focused tests to verify they pass**

Run: `cd go-backend && go test ./internal/web/handlers -run 'PostTablos|DeleteTablo'`

Expected: PASS for create/delete edge cases.

- [ ] **Step 5: Commit**

```bash
git add go-backend/internal/web/handlers/tablos.go go-backend/internal/web/handlers/tablos_test.go go-backend/internal/web/views/tablos.templ
git commit -m "feat: finalize tablo modal and delete UX"
```

### Task 6: Run full verification and record any gaps

**Files:**
- Modify: `docs/superpowers/plans/2026-05-08-go-backend-tablos-index-crud.md`

- [ ] **Step 1: Run full project tests**

Run: `cd go-backend && go test ./...`

Expected: PASS across handlers, router, and repository compile paths.

- [ ] **Step 2: Run generation and build verification**

Run: `cd go-backend && just generate`

Expected: PASS with no dirty generated output after generation.

Run: `cd go-backend && just build`

Expected: PASS with successful binary build.

- [ ] **Step 3: Re-read the approved spec and verify acceptance criteria manually**

Check:

- `/tablos` uses the agreed layout direction
- create opens a modal
- list, search, status filters, and view toggle are functional
- delete is soft delete and owner-scoped
- no custom app JavaScript was introduced

- [ ] **Step 4: Update the plan checklist and note any verification gaps**

If any command fails, record the exact failure under this plan before making completion claims.

- [ ] **Step 5: Commit**

```bash
git add docs/superpowers/plans/2026-05-08-go-backend-tablos-index-crud.md
git commit -m "docs: update tablo implementation plan status"
```

## Notes For Execution

- Prefer creating `go-backend/internal/web/handlers/tablos.go` rather than continuing to grow `auth.go`; the current codebase is still small enough to benefit from a clearer split.
- Keep the first slice owner-scoped only. Do not pull in organization/shared-access behavior from the legacy app.
- Keep `status` constrained in Go only. Do not add a DB enum or check constraint for this milestone.
- Preserve HTMX fragment boundaries carefully so the modal can fail inline without forcing a full page reload.
- If CSS support for the exact class contract is incomplete, implement the semantic structure first, then add the necessary stylesheet updates in a follow-up execution pass inside the same task chunk.

Plan complete and saved to `docs/superpowers/plans/2026-05-08-go-backend-tablos-index-crud.md`. Ready to execute?
