go-htmx-gsd #1
1 changed files with 287 additions and 0 deletions
|
|
@ -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.
|
||||
Loading…
Reference in a new issue