# Go Backend Tablos Index CRUD Design

**Date:** 2026-05-08

**Goal**

Build the first full `tablos` vertical slice in the Go rewrite: an authenticated `/tablos` page with server-rendered list, functional filtering/search/view controls, modal-based create, and soft delete.

**Scope**

- Add a real `tablos` table to the Go backend schema with:
  - `id`
  - `owner_id`
  - `name`
  - `status`
  - `created_at`
  - `updated_at`
  - `deleted_at`
- Render a real `/tablos` page in the rewrite app instead of placeholder content.
- Support listing the current user's non-deleted tablos.
- Support creating a tablo from a modal.
- Support soft deleting a tablo with `DELETE /tablos/:id`.
- Make grid/list toggle, search, and status filters functional.
- Keep the page server-rendered with HTMX partial updates.

**Out of Scope**

- Single project detail page at `/tablos/:id`
- Updating an existing tablo
- Multi-user sharing, access levels, or organization-owned tablos
- Database-level enum or constraint for `status`
- Custom frontend JavaScript beyond HTMX
- Client-side persistence for page UI state

**Architecture**

The feature should live entirely inside the Go rewrite stack:

- Postgres schema in `go-backend/internal/db/schema.sql`
- sqlc queries in `go-backend/internal/db/queries.sql`
- repository methods in `go-backend/internal/db/repository.go`
- HTTP handlers in `go-backend/internal/web/handlers/`
- `templ` views in `go-backend/internal/web/views/`
- route registration in `go-backend/router.go`

The page remains server-rendered. HTMX is used only to refresh the `/tablos` content region and modal content without introducing a SPA data layer.

**Data Model**

The `tablos` table should store:

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

The database stores `status` as plain text. The allowed values are enforced in Go, not in Postgres.

Initial allowed statuses:

- `todo`
- `in_progress`
- `done`

For this slice, create always writes `todo`.

`updated_at` should be maintained by application writes in the repository layer. Reads must always exclude rows where `deleted_at` is non-null.

**Runtime Validation**

Status validation belongs to Go code through a small domain type and helpers, for example:

- `type TabloStatus string`
- constants for `todo`, `in_progress`, `done`
- parse/validation helpers for query params and any future writes

The handler layer should treat invalid incoming `status` values as bad input for mutations and safe defaults for filter state.

**Route Design**

- `GET /tablos`
  - Returns the full dashboard page for normal requests.
  - Returns the swapped `/tablos` content fragment for HTMX requests.
  - Accepts query params:
    - `view=grid|list`
    - `q=<search text>`
    - `status=all|todo|in_progress|done`
- `POST /tablos`
  - Creates a new tablo for the authenticated user.
  - Reads form data from the modal.
  - Sets `status=todo` in Go.
  - On success, returns refreshed `/tablos` content in the current query state.
  - On validation failure, returns modal content with inline errors and status `422`.
- `DELETE /tablos/{id}`
  - Verifies ownership.
  - Sets `deleted_at` and updates `updated_at`.
  - Returns refreshed `/tablos` content in the current query state.

The delete request should preserve the current filter/search/view state by including the active query values in the HTMX request URL or values payload.

**Repository Design**

Extend the current repository abstraction beyond auth concerns so tablos can be stored and queried through explicit methods.

Minimum methods:

- list tablos for a user with optional filters
- create tablo
- soft delete tablo owned by a user

List semantics:

- scope by `owner_id = current user`
- filter out `deleted_at is not null`
- optional case-insensitive search on `name`
- optional status filter when status is not `all`
- deterministic ordering, preferably newest first via `created_at desc`

Delete semantics:

- only soft delete the row when `owner_id` matches the authenticated user
- treat already-deleted rows as not found for this slice

**UI Contract**

The `/tablos` page should follow the user-provided structure and class naming as the visual contract for the rewrite.

Top-level page shell:

```html
<main class="flex-1 overflow-auto">...</main>
```

The page should include:

- header row with `Mes Projets`
- a primary `Nouveau projet` button
- view toggle tabs for grid and list
- a search field
- status filter buttons for:
  - `Tous`
  - `Pas commencé`
  - `En cours`
  - `Terminé`
- a grid rendering mode matching the provided card structure
- a list rendering mode using the same dataset and controls

The layout, spacing, and class direction should track the supplied HTML closely rather than reusing the older app structure.

**Modal Create Flow**

`Nouveau projet` opens a modal dialog.

The modal should contain:

- a required `name` field
- submit action
- cancel/close action
- inline validation message region

The modal does not expose status in this slice because new tablos always start as `todo`.

HTMX behavior:

- open modal via server-rendered fragment or pre-rendered hidden dialog pattern
- submit modal with `POST /tablos`
- on success:
  - refresh the tablos page content
  - close the modal by returning the page fragment without an error state
- on failure:
  - keep the modal open
  - render inline validation feedback inside the modal

**Functional Controls**

The controls in the provided `/tablos` layout must be functional in this slice.

View toggle:

- URL-backed via `view=grid|list`
- HTMX `GET` updates the content region
- active tab styling reflects current state

Search:

- URL-backed via `q`
- server-side filtering
- HTMX submission with debounce
- preserves current `view` and `status`

Status filters:

- URL-backed via `status`
- values:
  - `all`
  - `todo`
  - `in_progress`
  - `done`
- preserve current `view` and `q`

Safe defaults:

- missing or invalid `view` defaults to `grid`
- missing or invalid `status` defaults to `all`
- empty `q` means no text filter

**Rendering States**

The page should render clear states for:

- empty result set with helpful copy and the same page controls still visible
- populated grid view
- populated list view
- validation failure in the create modal
- delete failure when the resource is missing or not owned

For this slice, inline and local feedback is preferred over a global toast system.

**HTMX Response Strategy**

Normal requests return the full dashboard page.

HTMX requests return only the relevant fragment(s):

- `/tablos` content swap for page refresh after search/filter/view changes
- modal content fragment for create validation failures
- optionally out-of-band updates if needed for modal close behavior

The implementation should keep fragment boundaries explicit so future `/tablos/:id` work can reuse the same dashboard composition pattern.

**Testing Strategy**

Use TDD across repository and handler layers.

Minimum repository coverage:

- create tablo sets `status=todo`
- list excludes soft-deleted rows
- list filters by status
- list filters by search query
- soft delete only affects the targeted owned row

Minimum handler coverage:

- authenticated `GET /tablos` renders full page
- authenticated HTMX `GET /tablos` renders content fragment
- `GET /tablos` honors `view`, `q`, and `status`
- `POST /tablos` with valid input creates and re-renders content
- `POST /tablos` with empty name returns `422` and modal errors
- `DELETE /tablos/{id}` soft deletes and re-renders content
- deleting another user's tablo fails

HTML assertions should verify:

- the page contains the agreed `Mes Projets` heading
- the `Nouveau projet` trigger exists
- grid and list modes produce distinguishable markup
- filter controls reflect active state
- modal validation feedback renders inline

**Risks and Mitigations**

- Extending an auth-focused repository into app data could blur responsibilities.
  - Mitigation: keep tablo methods explicit and narrow, and refactor to a broader app repository only if a second domain forces it.
- HTMX modal behavior can become messy if success and error fragments are not clearly separated.
  - Mitigation: define a small set of stable fragment boundaries before implementation.
- URL-backed controls can lose state across create/delete if query params are not preserved.
  - Mitigation: make current query state part of all HTMX requests that mutate or refresh the page.

**Acceptance Criteria**

- The Go rewrite has a real `tablos` table with the agreed columns.
- Visiting `/tablos` while authenticated shows a real projects page, not placeholder text.
- The page visually follows the supplied HTML structure and class direction.
- `Nouveau projet` opens a modal.
- Creating a tablo stores it for the authenticated user with `status=todo`.
- Deleting a tablo uses `DELETE /tablos/{id}` and sets `deleted_at`.
- Soft-deleted tablos no longer appear in reads.
- Grid/list toggle is functional.
- Search is functional.
- Status filters are functional.
- All state is server-driven and URL-backed where applicable.
- No custom application JavaScript is added for this slice.
- Targeted tests pass.
