go-htmx-gsd #1

Merged
arthur merged 558 commits from go-htmx-gsd into main 2026-05-23 15:16:44 +00:00
Showing only changes of commit 63882be278 - Show all commits

View file

@ -0,0 +1,287 @@
# 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.